bestguigui_lab 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/bestguigui_lab/assets.rb +28 -0
- data/lib/bestguigui_lab/gl_texture.rb +42 -0
- data/lib/bestguigui_lab/helpers.rb +10 -0
- data/lib/bestguigui_lab/input.rb +14 -0
- data/lib/bestguigui_lab/obj_model.rb +128 -0
- data/lib/bestguigui_lab/render_3d.rb +45 -0
- data/lib/bestguigui_lab/scene.rb +18 -0
- data/lib/bestguigui_lab/splash_scene.rb +181 -0
- data/lib/bestguigui_lab/version.rb +5 -0
- data/lib/bestguigui_lab.rb +15 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 96f6d3fcc750e5d014248e718d7002117e19cdbe90b3787bc7709e2bf20e940c
|
|
4
|
+
data.tar.gz: 4f3e25a6ccade65ceb49178db07e71d70ace4d9afb81482a4bd19089b8ef8809
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0512a0c2081412ef87f90d19690e38ba315eebd4ef51eb2f13047538384cf30f149db57bc46a216f30f1d37f3bd7a7800f630ca3ec0da9c7d9a32b5028b4f82b
|
|
7
|
+
data.tar.gz: 922ce4a677d389b45eb98272a260c83d620070da48f14979609e2738782f5ff61602a3b7750c19ccbd3f8677b54a520e3a3d816a621a8843b4eb2e03811ac871
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Charge et met en cache les assets (images, sons, modèles 3D...) pour éviter de les recharger plusieurs fois.
|
|
5
|
+
class Assets
|
|
6
|
+
@root = Dir.pwd
|
|
7
|
+
|
|
8
|
+
@images = Hash.new { |cache, path| cache[path] = Gosu::Image.new(path, retro: true) }
|
|
9
|
+
@sounds = Hash.new { |cache, path| cache[path] = Gosu::Sample.new(path) }
|
|
10
|
+
@songs = Hash.new { |cache, path| cache[path] = Gosu::Song.new(path) }
|
|
11
|
+
@fonts = Hash.new { |cache, key| cache[key] = Gosu::Font.new(key[1], name: key[0]) }
|
|
12
|
+
@models = Hash.new { |cache, path| cache[path] = ObjModel.new(path) }
|
|
13
|
+
@textures = Hash.new { |cache, path| cache[path] = GLTexture.new(path) }
|
|
14
|
+
|
|
15
|
+
def self.resolve(name) = File.expand_path(name, @root)
|
|
16
|
+
def self.image(name) = @images[resolve(name)]
|
|
17
|
+
def self.sound(name) = @sounds[resolve(name)]
|
|
18
|
+
def self.song(name) = @songs[resolve(name)]
|
|
19
|
+
|
|
20
|
+
def self.font(name = Gosu.default_font_name, size: 20)
|
|
21
|
+
path = name == Gosu.default_font_name ? name : resolve(name)
|
|
22
|
+
@fonts[[path, size]]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.model(name) = @models[resolve(name)]
|
|
26
|
+
def self.texture(name) = @textures[resolve(name)]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Texture OpenGL indépendante de l'atlas partagé de Gosu, construite à
|
|
5
|
+
# partir des pixels bruts (`to_blob`) — permet un vrai GL_REPEAT natif.
|
|
6
|
+
class GLTexture
|
|
7
|
+
attr_reader :tex_name
|
|
8
|
+
|
|
9
|
+
def initialize(path)
|
|
10
|
+
super()
|
|
11
|
+
image = Gosu::Image.new(path, retro: true)
|
|
12
|
+
@tex_name = upload(image)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def upload(image)
|
|
18
|
+
name_buffer = "\x00" * 4
|
|
19
|
+
glGenTextures(1, name_buffer)
|
|
20
|
+
tex_name = name_buffer.unpack1('L')
|
|
21
|
+
|
|
22
|
+
# Garder les pixels dans une variable (pas `image.to_blob` inline
|
|
23
|
+
# en argument) : sinon Ruby peut libérer la String pendant
|
|
24
|
+
# l'appel FFI, avant qu'OpenGL l'ait lue (texture à zéro, sans
|
|
25
|
+
# erreur OpenGL).
|
|
26
|
+
pixels = image.to_blob
|
|
27
|
+
|
|
28
|
+
glBindTexture(GL_TEXTURE_2D, tex_name)
|
|
29
|
+
configure_wrapping
|
|
30
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
|
|
31
|
+
|
|
32
|
+
tex_name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure_wrapping
|
|
36
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
|
37
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
|
38
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
|
39
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Petites fonctions utilitaires pour les animations (interpolation, easing).
|
|
5
|
+
class Helpers
|
|
6
|
+
def self.lerp(from, dest, progress) = from + ((dest - from) * progress)
|
|
7
|
+
|
|
8
|
+
def self.ease_out(progress) = 1 - ((1 - progress)**2)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Mappe une action à plusieurs touches/boutons (ex. right: [KB_RIGHT, KB_D]).
|
|
5
|
+
class Input
|
|
6
|
+
def initialize(bindings = {})
|
|
7
|
+
@bindings = bindings
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def button_down?(action) = @bindings[action].any? { |key| Gosu.button_down?(key) }
|
|
11
|
+
def button_down(action, id) = @bindings[action].any? { |key| key == id }
|
|
12
|
+
def button_up(action, id) = @bindings[action].any? { |key| key == id }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Charge un modèle .obj/.mtl et le dessine via un VBO : géométrie
|
|
5
|
+
class ObjModel
|
|
6
|
+
# position (3) + texcoord (2) + normale (3) par sommet.
|
|
7
|
+
FLOATS_PER_VERTEX = 8
|
|
8
|
+
STRIDE = FLOATS_PER_VERTEX * 4 # 4 octets par float
|
|
9
|
+
|
|
10
|
+
def initialize(obj_path)
|
|
11
|
+
super()
|
|
12
|
+
@vertices = []
|
|
13
|
+
@tex_coords = []
|
|
14
|
+
@normals = []
|
|
15
|
+
@faces = []
|
|
16
|
+
@texture = nil
|
|
17
|
+
|
|
18
|
+
parse_obj(obj_path)
|
|
19
|
+
@vertex_count = @faces.size
|
|
20
|
+
@vbo = build_vertex_buffer
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def draw
|
|
24
|
+
glEnable(GL_TEXTURE_2D)
|
|
25
|
+
glBindTexture(GL_TEXTURE_2D, @texture.tex_name)
|
|
26
|
+
glBindBuffer(GL_ARRAY_BUFFER, @vbo)
|
|
27
|
+
|
|
28
|
+
enable_vertex_arrays
|
|
29
|
+
glDrawArrays(GL_TRIANGLES, 0, @vertex_count)
|
|
30
|
+
disable_vertex_arrays
|
|
31
|
+
|
|
32
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
33
|
+
glDisable(GL_TEXTURE_2D)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def parse_obj(path)
|
|
39
|
+
directory = File.dirname(path)
|
|
40
|
+
|
|
41
|
+
File.foreach(path) do |line|
|
|
42
|
+
tokens = line.split
|
|
43
|
+
next if tokens.empty?
|
|
44
|
+
|
|
45
|
+
keyword = tokens.shift
|
|
46
|
+
parse_line(keyword, tokens, directory)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_line(keyword, tokens, directory)
|
|
51
|
+
case keyword
|
|
52
|
+
when 'v', 'vt', 'vn' then parse_coordinates(keyword, tokens)
|
|
53
|
+
when 'f' then @faces.concat(parse_face(tokens))
|
|
54
|
+
when 'mtllib' then load_material(File.join(directory, tokens.first))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_coordinates(keyword, tokens)
|
|
59
|
+
values = tokens.map(&:to_f)
|
|
60
|
+
|
|
61
|
+
case keyword
|
|
62
|
+
when 'v' then @vertices << values
|
|
63
|
+
when 'vt' then @tex_coords << values
|
|
64
|
+
when 'vn' then @normals << values
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# "f" contient des groupes "v/vt/vn" (indices 1-indexés, vt/vn
|
|
69
|
+
# parfois absents).
|
|
70
|
+
def parse_face(tokens)
|
|
71
|
+
tokens.map do |group|
|
|
72
|
+
v, vt, vn = group.split('/').map { |i| i.nil? || i.empty? ? nil : i.to_i - 1 }
|
|
73
|
+
[v, vt, vn]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Un seul matériau par modèle : la dernière texture (map_Kd) trouvée.
|
|
78
|
+
def load_material(path)
|
|
79
|
+
directory = File.dirname(path)
|
|
80
|
+
|
|
81
|
+
File.foreach(path) do |line|
|
|
82
|
+
tokens = line.split
|
|
83
|
+
next if tokens.empty? || tokens.shift != 'map_Kd'
|
|
84
|
+
|
|
85
|
+
@texture = GLTexture.new(File.join(directory, tokens.first))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Buffer plat non indexé : les sommets partagés sont dupliqués, pas
|
|
90
|
+
# d'index buffer pour rester simple.
|
|
91
|
+
def build_vertex_buffer
|
|
92
|
+
data = @faces.flat_map { |v, vt, vn| vertex_attributes(v, vt, vn) }.pack('F*')
|
|
93
|
+
|
|
94
|
+
name_buffer = "\x00" * 4
|
|
95
|
+
glGenBuffers(1, name_buffer)
|
|
96
|
+
vbo = name_buffer.unpack1('L')
|
|
97
|
+
|
|
98
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
|
99
|
+
glBufferData(GL_ARRAY_BUFFER, data.bytesize, data, GL_STATIC_DRAW)
|
|
100
|
+
|
|
101
|
+
vbo
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def vertex_attributes(vertex_index, tex_coord_index, normal_index)
|
|
105
|
+
position = @vertices[vertex_index]
|
|
106
|
+
tex_coord = tex_coord_index ? @tex_coords[tex_coord_index] : [0.0, 0.0]
|
|
107
|
+
normal = normal_index ? @normals[normal_index] : [0.0, 0.0, 1.0]
|
|
108
|
+
|
|
109
|
+
position + tex_coord + normal
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def enable_vertex_arrays
|
|
113
|
+
glEnableClientState(GL_VERTEX_ARRAY)
|
|
114
|
+
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
|
|
115
|
+
glEnableClientState(GL_NORMAL_ARRAY)
|
|
116
|
+
|
|
117
|
+
glVertexPointer(3, GL_FLOAT, STRIDE, 0)
|
|
118
|
+
glTexCoordPointer(2, GL_FLOAT, STRIDE, 3 * 4)
|
|
119
|
+
glNormalPointer(GL_FLOAT, STRIDE, 5 * 4)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def disable_vertex_arrays
|
|
123
|
+
glDisableClientState(GL_NORMAL_ARRAY)
|
|
124
|
+
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
|
|
125
|
+
glDisableClientState(GL_VERTEX_ARRAY)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Instancie-la une fois, garde une référence, utilise-la dans draw :
|
|
5
|
+
# @renderer = Render3D.new
|
|
6
|
+
# @renderer.render_3d(self) { @renderer.look_at(...) ; @model.draw }
|
|
7
|
+
class Render3D
|
|
8
|
+
# fov : angle de champ de vision vertical, en degrés.
|
|
9
|
+
# clip : [proche, lointain] — tout ce qui est en dehors n'est pas dessiné.
|
|
10
|
+
def render_3d(window, z_order: 0, fov: 60.0, clip: [0.1, 1000.0], &block)
|
|
11
|
+
w, h = window.fullscreen? ? [Gosu.screen_width, Gosu.screen_height] : [window.width, window.height]
|
|
12
|
+
Gosu.gl(z_order) { render_3d_frame(w, h, fov, clip, &block) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# À appeler dans le bloc de render_3d, avant de dessiner, pour
|
|
16
|
+
# positionner la caméra : eye = position, center = point regardé.
|
|
17
|
+
def look_at(eye, center, up_vector = [0, 1, 0])
|
|
18
|
+
gluLookAt(*eye, *center, *up_vector)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Gosu.gl isole déjà l'état (glPushAttrib/glPopAttrib) et remet sa
|
|
24
|
+
# propre projection/modelview en sortant du bloc — pas besoin de
|
|
25
|
+
# push/pop matrice de notre côté, juste de poser nos valeurs.
|
|
26
|
+
def render_3d_frame(width, height, fov, clip)
|
|
27
|
+
glEnable(GL_DEPTH_TEST)
|
|
28
|
+
glDepthFunc(GL_LESS)
|
|
29
|
+
|
|
30
|
+
setup_projection(width, height, fov, clip)
|
|
31
|
+
glMatrixMode(GL_MODELVIEW)
|
|
32
|
+
glLoadIdentity
|
|
33
|
+
|
|
34
|
+
yield
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def setup_projection(width, height, fov, clip)
|
|
38
|
+
near, far = clip
|
|
39
|
+
|
|
40
|
+
glMatrixMode(GL_PROJECTION)
|
|
41
|
+
glLoadIdentity
|
|
42
|
+
gluPerspective(fov, width.to_f / height, near, far)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Classe de base pour une scène (écran de jeu, menu, victoire...).
|
|
5
|
+
# Hérite-en et ne redéfinis que ce dont tu as besoin.
|
|
6
|
+
class Scene
|
|
7
|
+
def initialize(window)
|
|
8
|
+
@window = window
|
|
9
|
+
@font = Assets.font(size: 24)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def button_down(id); end
|
|
13
|
+
|
|
14
|
+
def update(dt); end
|
|
15
|
+
|
|
16
|
+
def draw; end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bestguigui
|
|
4
|
+
# Le rubis animé du splash screen : tombe du haut de l'écran, touche le
|
|
5
|
+
# titre, rebondit au-dessus, puis se stabilise. Dessiné en primitives
|
|
6
|
+
# Gosu (aucune image).
|
|
7
|
+
class SplashGem
|
|
8
|
+
SIZE = 220
|
|
9
|
+
GAP = 40
|
|
10
|
+
OVERSHOOT = 12
|
|
11
|
+
FALL = 0.5
|
|
12
|
+
BOUNCE_UP = 0.25
|
|
13
|
+
SETTLE = 0.2
|
|
14
|
+
IMPACT_DIP = 10
|
|
15
|
+
|
|
16
|
+
# Points du diamant, en fractions (0..1) d'une boîte carrée.
|
|
17
|
+
POINTS = {
|
|
18
|
+
tl: [0.30, 0.32],
|
|
19
|
+
tr: [0.70, 0.32],
|
|
20
|
+
l: [0.12, 0.45],
|
|
21
|
+
r: [0.88, 0.45],
|
|
22
|
+
hub: [0.50, 0.47],
|
|
23
|
+
bottom: [0.50, 0.88]
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
HIGHLIGHT = [240, 150, 155].freeze
|
|
27
|
+
BASE = [210, 90, 95].freeze
|
|
28
|
+
DARK = [180, 60, 65].freeze
|
|
29
|
+
OUTLINE_RGB = [40, 30, 40].freeze
|
|
30
|
+
|
|
31
|
+
FACETS = [
|
|
32
|
+
[%i[tl hub l], HIGHLIGHT],
|
|
33
|
+
[%i[tl tr hub], BASE],
|
|
34
|
+
[%i[tr r hub], DARK],
|
|
35
|
+
[%i[l hub bottom], HIGHLIGHT],
|
|
36
|
+
[%i[r bottom hub], DARK]
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
OUTLINE = %i[tl tr r bottom l].freeze
|
|
40
|
+
|
|
41
|
+
def initialize(window)
|
|
42
|
+
@window = window
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def draw(elapsed, alpha)
|
|
46
|
+
center_x = @window.width / 2
|
|
47
|
+
center_y = center_y(elapsed)
|
|
48
|
+
|
|
49
|
+
FACETS.each { |names, rgb| draw_facet(names, rgb, center_x, center_y, alpha) }
|
|
50
|
+
draw_outline(center_x, center_y, alpha)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def title_offset(elapsed)
|
|
54
|
+
return 0 if elapsed < FALL
|
|
55
|
+
|
|
56
|
+
if elapsed < FALL + BOUNCE_UP
|
|
57
|
+
Helpers.lerp(IMPACT_DIP, 0, Helpers.ease_out((elapsed - FALL) / BOUNCE_UP))
|
|
58
|
+
else
|
|
59
|
+
0
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def center_y(elapsed)
|
|
66
|
+
if elapsed < FALL
|
|
67
|
+
fall_y(elapsed)
|
|
68
|
+
elsif elapsed < FALL + BOUNCE_UP
|
|
69
|
+
bounce_up_y(elapsed)
|
|
70
|
+
elsif elapsed < FALL + BOUNCE_UP + SETTLE
|
|
71
|
+
settle_y(elapsed)
|
|
72
|
+
else
|
|
73
|
+
rest_y
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def impact_y = (@window.height / 2) - bottom_offset
|
|
78
|
+
|
|
79
|
+
def bottom_offset = (POINTS[:bottom][1] - 0.6) * SIZE
|
|
80
|
+
|
|
81
|
+
def rest_y = impact_y - GAP
|
|
82
|
+
|
|
83
|
+
def top_y = rest_y - OVERSHOOT
|
|
84
|
+
|
|
85
|
+
def fall_y(elapsed) = Helpers.lerp(-SIZE, impact_y, Helpers.ease_out(elapsed / FALL))
|
|
86
|
+
|
|
87
|
+
def bounce_up_y(elapsed)
|
|
88
|
+
progress = Helpers.ease_out((elapsed - FALL) / BOUNCE_UP)
|
|
89
|
+
Helpers.lerp(impact_y, top_y, progress)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def settle_y(elapsed)
|
|
93
|
+
progress = Helpers.ease_out((elapsed - FALL - BOUNCE_UP) / SETTLE)
|
|
94
|
+
Helpers.lerp(top_y, rest_y, progress)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def draw_facet(names, rgb, center_x, center_y, alpha)
|
|
98
|
+
color = Gosu::Color.new(alpha, *rgb)
|
|
99
|
+
a, b, c = names.map { |name| point(name, center_x, center_y) }
|
|
100
|
+
Gosu.draw_triangle(a[0], a[1], color, b[0], b[1], color, c[0], c[1], color)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def draw_outline(center_x, center_y, alpha)
|
|
104
|
+
color = Gosu::Color.new(alpha, *OUTLINE_RGB)
|
|
105
|
+
points = OUTLINE.map { |name| point(name, center_x, center_y) }
|
|
106
|
+
points.each_with_index do |p, i|
|
|
107
|
+
next_p = points[(i + 1) % points.size]
|
|
108
|
+
Gosu.draw_line(p[0], p[1], color, next_p[0], next_p[1], color)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def point(name, center_x, center_y)
|
|
113
|
+
fx, fy = POINTS[name]
|
|
114
|
+
[center_x + ((fx - 0.5) * SIZE), center_y + ((fy - 0.6) * SIZE)]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Écran de démarrage prêt à l'emploi : titre + "Powered by Gosu" en fondu,
|
|
119
|
+
# avec un SplashGem qui tombe et rebondit sur le titre. Bascule ensuite
|
|
120
|
+
# sur la scène suivante.
|
|
121
|
+
#
|
|
122
|
+
# Bestguigui::SplashScene.new(window, title: "Mon jeu", next_scene: :menu)
|
|
123
|
+
class SplashScene < Scene
|
|
124
|
+
FADE_IN = 0.6
|
|
125
|
+
HOLD = 1.2
|
|
126
|
+
FADE_OUT = 0.6
|
|
127
|
+
|
|
128
|
+
def initialize(window, title:, next_scene:)
|
|
129
|
+
super(window)
|
|
130
|
+
@title = title
|
|
131
|
+
@next_scene = next_scene
|
|
132
|
+
@gem = SplashGem.new(window)
|
|
133
|
+
@title_font = Assets.font(size: 64)
|
|
134
|
+
@subtitle_font = Assets.font(size: 24)
|
|
135
|
+
@gosu_font = Assets.font(size: 36)
|
|
136
|
+
@elapsed = 0.0
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def update(dt)
|
|
140
|
+
@elapsed += dt / 1000.0
|
|
141
|
+
@window.switch_scene(@next_scene) if @elapsed >= FADE_IN + HOLD + FADE_OUT
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def draw
|
|
145
|
+
color = Gosu::Color.new(fade_alpha, 255, 255, 255)
|
|
146
|
+
accent = Gosu::Color.new(fade_alpha, 220, 30, 40)
|
|
147
|
+
offset = @gem.title_offset(@elapsed)
|
|
148
|
+
|
|
149
|
+
@gem.draw(@elapsed, fade_alpha)
|
|
150
|
+
draw_centered(@title_font, @title, (@window.height / 2) + offset, color)
|
|
151
|
+
draw_powered_by((@window.height / 2) + 80 + offset, color, accent)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def draw_powered_by(top, color, accent)
|
|
157
|
+
prefix_width = @subtitle_font.text_width('Powered by ')
|
|
158
|
+
total_width = prefix_width + @gosu_font.text_width('Gosu')
|
|
159
|
+
x = (@window.width - total_width) / 2
|
|
160
|
+
|
|
161
|
+
@subtitle_font.draw_text('Powered by ', x, top + 6, 0, 1, 1, color)
|
|
162
|
+
@gosu_font.draw_text('Gosu', x + prefix_width, top, 0, 1, 1, accent)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def fade_alpha
|
|
166
|
+
if @elapsed < FADE_IN
|
|
167
|
+
(255 * (@elapsed / FADE_IN)).to_i
|
|
168
|
+
elsif @elapsed < FADE_IN + HOLD
|
|
169
|
+
255
|
|
170
|
+
else
|
|
171
|
+
remaining = FADE_IN + HOLD + FADE_OUT - @elapsed
|
|
172
|
+
(255 * (remaining / FADE_OUT)).clamp(0, 255).to_i
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def draw_centered(font, text, top, color)
|
|
177
|
+
x = (@window.width - font.text_width(text)) / 2
|
|
178
|
+
font.draw_text(text, x, top, 0, 1, 1, color)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gosu'
|
|
4
|
+
require 'opengl'
|
|
5
|
+
require 'glu'
|
|
6
|
+
|
|
7
|
+
include OpenGL
|
|
8
|
+
include GLU
|
|
9
|
+
|
|
10
|
+
# Sans ça, les fonctions OpenGL au-delà de la version 1.1 plantent en
|
|
11
|
+
# silence (NoMethodError sur nil).
|
|
12
|
+
OpenGL.load_lib
|
|
13
|
+
GLU.load_lib
|
|
14
|
+
|
|
15
|
+
Dir[File.join(__dir__, 'bestguigui_lab', '*.rb')].each { |fn| require_relative fn }
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bestguigui_lab
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bestguigui
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: gosu
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.4'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: opengl-bindings
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.6'
|
|
40
|
+
executables: []
|
|
41
|
+
extensions: []
|
|
42
|
+
extra_rdoc_files: []
|
|
43
|
+
files:
|
|
44
|
+
- lib/bestguigui_lab.rb
|
|
45
|
+
- lib/bestguigui_lab/assets.rb
|
|
46
|
+
- lib/bestguigui_lab/gl_texture.rb
|
|
47
|
+
- lib/bestguigui_lab/helpers.rb
|
|
48
|
+
- lib/bestguigui_lab/input.rb
|
|
49
|
+
- lib/bestguigui_lab/obj_model.rb
|
|
50
|
+
- lib/bestguigui_lab/render_3d.rb
|
|
51
|
+
- lib/bestguigui_lab/scene.rb
|
|
52
|
+
- lib/bestguigui_lab/splash_scene.rb
|
|
53
|
+
- lib/bestguigui_lab/version.rb
|
|
54
|
+
homepage: https://github.com/guillaumequillet/bestguigui_lab
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
rubygems_mfa_required: 'true'
|
|
59
|
+
rdoc_options: []
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.0'
|
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubygems_version: 4.0.16
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: A Game Jam Starter Kit to optimize Game Jams
|
|
76
|
+
test_files: []
|