red_bird 0.1.1
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/.gitignore +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +47 -0
- data/Rakefile +10 -0
- data/bin/setup +8 -0
- data/ext/red_bird/bird.c +15 -0
- data/ext/red_bird/bird.h +10 -0
- data/ext/red_bird/color.c +95 -0
- data/ext/red_bird/color.h +27 -0
- data/ext/red_bird/dynamic_sprite.c +163 -0
- data/ext/red_bird/dynamic_sprite.h +30 -0
- data/ext/red_bird/engine.c +354 -0
- data/ext/red_bird/engine.h +40 -0
- data/ext/red_bird/extconf.rb +9 -0
- data/ext/red_bird/font.c +94 -0
- data/ext/red_bird/font.h +26 -0
- data/ext/red_bird/input_device.c +100 -0
- data/ext/red_bird/input_device.h +15 -0
- data/ext/red_bird/keycode.c +42 -0
- data/ext/red_bird/keycode.h +12 -0
- data/ext/red_bird/loader.c +154 -0
- data/ext/red_bird/loader.h +54 -0
- data/ext/red_bird/main.c +38 -0
- data/ext/red_bird/main.h +12 -0
- data/ext/red_bird/palette.c +132 -0
- data/ext/red_bird/palette.h +23 -0
- data/ext/red_bird/rect.c +257 -0
- data/ext/red_bird/rect.h +20 -0
- data/ext/red_bird/render.c +130 -0
- data/ext/red_bird/render.h +25 -0
- data/ext/red_bird/sprite.c +130 -0
- data/ext/red_bird/sprite.h +27 -0
- data/ext/red_bird/text.c +212 -0
- data/ext/red_bird/text.h +31 -0
- data/ext/red_bird/texture.c +157 -0
- data/ext/red_bird/texture.h +33 -0
- data/ext/red_bird/texture_imp.cpp +49 -0
- data/ext/red_bird/texture_imp.hpp +29 -0
- data/ext/red_bird/timer.c +134 -0
- data/ext/red_bird/timer.h +25 -0
- data/lib/red_bird.rb +15 -0
- data/lib/red_bird/animation.rb +133 -0
- data/lib/red_bird/camera.rb +61 -0
- data/lib/red_bird/controller.rb +44 -0
- data/lib/red_bird/dynamic_sprite.rb +38 -0
- data/lib/red_bird/engine.rb +81 -0
- data/lib/red_bird/entity.rb +74 -0
- data/lib/red_bird/entity_collision.rb +31 -0
- data/lib/red_bird/input_device.rb +86 -0
- data/lib/red_bird/palette.rb +23 -0
- data/lib/red_bird/relative_entity.rb +95 -0
- data/lib/red_bird/sprite.rb +40 -0
- data/lib/red_bird/stage.rb +60 -0
- data/lib/red_bird/tile_map.rb +118 -0
- data/lib/red_bird/tile_set.rb +56 -0
- data/lib/red_bird/uibox.rb +143 -0
- data/lib/red_bird/version.rb +3 -0
- data/lib/red_bird/vertical_menu.rb +110 -0
- data/red_bird.gemspec +37 -0
- metadata +149 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
#ifndef RED_BIRD_TEXTURE_H
|
3
|
+
#define RED_BIRD_TEXTURE_H 1
|
4
|
+
|
5
|
+
#include "main.h"
|
6
|
+
|
7
|
+
extern VALUE bird_cTexture;
|
8
|
+
|
9
|
+
struct bird_texture_data
|
10
|
+
{
|
11
|
+
SDL_Texture *data;
|
12
|
+
int width, height;
|
13
|
+
};
|
14
|
+
|
15
|
+
VALUE
|
16
|
+
bird_alloc_texture(VALUE klass);
|
17
|
+
|
18
|
+
VALUE
|
19
|
+
bird_cTexture_initialize(VALUE self, VALUE file_path, VALUE palette);
|
20
|
+
|
21
|
+
VALUE
|
22
|
+
bird_cTexture_width(VALUE self);
|
23
|
+
|
24
|
+
VALUE
|
25
|
+
bird_cTexture_height(VALUE self);
|
26
|
+
|
27
|
+
struct bird_texture_data*
|
28
|
+
bird_cTexture_get_data(VALUE self);
|
29
|
+
|
30
|
+
void
|
31
|
+
Init_red_bird_texture(void);
|
32
|
+
|
33
|
+
#endif /* RED_BIRD_TEXTURE_H */
|
@@ -0,0 +1,49 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
#include "texture_imp.hpp"
|
3
|
+
|
4
|
+
#include <charconv>
|
5
|
+
#include <fstream>
|
6
|
+
#include <string>
|
7
|
+
|
8
|
+
SDL_bool
|
9
|
+
bird_cTexture_PGM_load(
|
10
|
+
struct bird_cTexture_PGM *pgm_img, const char *file_path)
|
11
|
+
{
|
12
|
+
int wh_pos;
|
13
|
+
std::string line;
|
14
|
+
std::ifstream file(file_path);
|
15
|
+
|
16
|
+
if(!file) return SDL_FALSE;
|
17
|
+
|
18
|
+
// Read file magic.
|
19
|
+
std::getline(file, line);
|
20
|
+
if(line != "P5") return SDL_FALSE;
|
21
|
+
|
22
|
+
// Read file comment.
|
23
|
+
std::getline(file, line);
|
24
|
+
|
25
|
+
// Read file width and height.
|
26
|
+
std::getline(file, line);
|
27
|
+
wh_pos = line.find(" ");
|
28
|
+
std::from_chars(line.data(), line.data() + wh_pos, pgm_img->width);
|
29
|
+
std::from_chars(
|
30
|
+
line.data() + wh_pos + 1, line.data() + line.size(), pgm_img->height);
|
31
|
+
|
32
|
+
// Read file maximum value.
|
33
|
+
std::getline(file, line);
|
34
|
+
std::from_chars(line.data(), line.data() + line.size(), pgm_img->max_value);
|
35
|
+
|
36
|
+
// Read file values.
|
37
|
+
std::getline(file, line);
|
38
|
+
pgm_img->data_size = line.size();
|
39
|
+
pgm_img->data = new char[pgm_img->data_size];
|
40
|
+
memcpy(pgm_img->data, line.data(), pgm_img->data_size);
|
41
|
+
|
42
|
+
return SDL_TRUE;
|
43
|
+
}
|
44
|
+
|
45
|
+
void
|
46
|
+
bird_cTexture_PGM_unload(bird_cTexture_PGM *pgm_img)
|
47
|
+
{
|
48
|
+
delete[] pgm_img->data;
|
49
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
#ifndef RED_BIRD_TEXTURE_IMP_H
|
3
|
+
#define RED_BIRD_TEXTURE_IMP_H 1
|
4
|
+
|
5
|
+
#ifdef __cplusplus
|
6
|
+
extern "C"
|
7
|
+
{
|
8
|
+
#endif
|
9
|
+
|
10
|
+
#include "main.h"
|
11
|
+
|
12
|
+
struct bird_cTexture_PGM
|
13
|
+
{
|
14
|
+
int width, height, max_value, data_size;
|
15
|
+
char *data;
|
16
|
+
};
|
17
|
+
|
18
|
+
SDL_bool
|
19
|
+
bird_cTexture_PGM_load(
|
20
|
+
struct bird_cTexture_PGM *pgm_img, const char *file_path);
|
21
|
+
|
22
|
+
void
|
23
|
+
bird_cTexture_PGM_unload(struct bird_cTexture_PGM *pgm_img);
|
24
|
+
|
25
|
+
#ifdef __cplusplus
|
26
|
+
}
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#endif /* RED_BIRD_TEXTURE_IMP_H */
|
@@ -0,0 +1,134 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
#include "timer.h"
|
3
|
+
|
4
|
+
#include "engine.h"
|
5
|
+
|
6
|
+
/*
|
7
|
+
Document-class: RedBird::Timer
|
8
|
+
|
9
|
+
A Timer controls the speed of execution of a frame; it prevents that a frame
|
10
|
+
run too quickly and give you compensation when a frame runs too slow.
|
11
|
+
|
12
|
+
When creating a new instance, you must define the maximum frame rate that you
|
13
|
+
want; then, you should call {#tick} at every frame to get a delta time. The
|
14
|
+
delta time is proportional to the frame rate defined during the creation of
|
15
|
+
the instance or is higher when the last frame was slower than expected.
|
16
|
+
|
17
|
+
@author Frederico Linhares
|
18
|
+
*/
|
19
|
+
VALUE bird_cTimer;
|
20
|
+
|
21
|
+
/*
|
22
|
+
Basic functions all Ruby classes need.
|
23
|
+
*/
|
24
|
+
|
25
|
+
static void
|
26
|
+
bird_free_timer(void* obj)
|
27
|
+
{
|
28
|
+
struct bird_timer_data *ptr = obj;
|
29
|
+
|
30
|
+
free(ptr);
|
31
|
+
}
|
32
|
+
|
33
|
+
static size_t
|
34
|
+
bird_memsize_timer(const void* obj)
|
35
|
+
{
|
36
|
+
// TODO
|
37
|
+
return 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
static const rb_data_type_t
|
41
|
+
bird_timer_type = {
|
42
|
+
"red_bird_timer",
|
43
|
+
{0, bird_free_timer, bird_memsize_timer,},
|
44
|
+
0, 0,
|
45
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
46
|
+
};
|
47
|
+
|
48
|
+
static VALUE
|
49
|
+
bird_alloc_timer(VALUE klass)
|
50
|
+
{
|
51
|
+
VALUE obj;
|
52
|
+
struct bird_timer_data *ptr;
|
53
|
+
|
54
|
+
obj = TypedData_Make_Struct(klass, struct bird_timer_data, &bird_timer_type,
|
55
|
+
ptr);
|
56
|
+
|
57
|
+
return obj;
|
58
|
+
}
|
59
|
+
|
60
|
+
/*
|
61
|
+
@param max_fps [Integer] the maximum of frames to be rendered per second.
|
62
|
+
@author Frederico Linhares
|
63
|
+
*/
|
64
|
+
VALUE
|
65
|
+
bird_cTimer_initialize(VALUE self, VALUE max_fps)
|
66
|
+
{
|
67
|
+
struct bird_timer_data *ptr;
|
68
|
+
|
69
|
+
if(!engine_initialized)
|
70
|
+
rb_raise(rb_eRuntimeError, "%s",
|
71
|
+
"can not create a RedBird::Timer instance before "
|
72
|
+
"RedBird::Engine is started");
|
73
|
+
|
74
|
+
RB_INTEGER_TYPE_P(max_fps);
|
75
|
+
|
76
|
+
TypedData_Get_Struct(self, struct bird_timer_data, &bird_timer_type, ptr);
|
77
|
+
ptr->max_frame_duration_ms = 1000 / NUM2INT(max_fps);
|
78
|
+
ptr->max_frame_duration_sec = ptr->max_frame_duration_ms/1000.0;
|
79
|
+
ptr->frame_start = SDL_GetTicks();
|
80
|
+
|
81
|
+
return self;
|
82
|
+
}
|
83
|
+
|
84
|
+
/*
|
85
|
+
Every time you call this method, it compares the current time with the last
|
86
|
+
time it was called; if the amount of time is inferior to the frame duration,
|
87
|
+
it waits to ensure the frame has the exact duration expected; otherwise, it
|
88
|
+
returns instantly and gives you a delta value that compensates for the extra
|
89
|
+
duration of the last frame.
|
90
|
+
|
91
|
+
@return [Float] represents the amount of time between this frame and the last
|
92
|
+
frame, the higher, the higher amount of time has passed.
|
93
|
+
@author Frederico Linhares
|
94
|
+
*/
|
95
|
+
VALUE
|
96
|
+
bird_cTimer_tick(VALUE self)
|
97
|
+
{
|
98
|
+
struct bird_timer_data *ptr;
|
99
|
+
Uint32 frame_stop;
|
100
|
+
Uint32 frame_duration;
|
101
|
+
VALUE delta;
|
102
|
+
|
103
|
+
TypedData_Get_Struct(self, struct bird_timer_data, &bird_timer_type, ptr);
|
104
|
+
|
105
|
+
frame_stop = SDL_GetTicks();
|
106
|
+
frame_duration = frame_stop - ptr->frame_start;
|
107
|
+
|
108
|
+
// If frame take less time than maximum allowed.
|
109
|
+
if(ptr->max_frame_duration_ms > frame_duration)
|
110
|
+
{
|
111
|
+
SDL_Delay(ptr->max_frame_duration_ms - frame_duration);
|
112
|
+
delta = rb_float_new(ptr->max_frame_duration_sec);
|
113
|
+
}
|
114
|
+
// If frame take too long time.
|
115
|
+
else
|
116
|
+
{
|
117
|
+
// SDL_GetTicks return time im miliseconds, so I need to divide by 1000 to
|
118
|
+
// get the time in seconds.
|
119
|
+
delta = rb_float_new((double)frame_duration/1000.0);
|
120
|
+
}
|
121
|
+
|
122
|
+
ptr->frame_start = frame_stop;
|
123
|
+
|
124
|
+
return delta;
|
125
|
+
}
|
126
|
+
|
127
|
+
void
|
128
|
+
Init_red_bird_timer(void)
|
129
|
+
{
|
130
|
+
bird_cTimer = rb_define_class_under(bird_m, "Timer", rb_cData);
|
131
|
+
rb_define_alloc_func(bird_cTimer, bird_alloc_timer);
|
132
|
+
rb_define_method(bird_cTimer, "initialize", bird_cTimer_initialize, 1);
|
133
|
+
rb_define_method(bird_cTimer, "tick", bird_cTimer_tick, 0);
|
134
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
#ifndef RED_BIRD_TIMER_H
|
3
|
+
#define RED_BIRD_TIMER_H 1
|
4
|
+
|
5
|
+
#include "main.h"
|
6
|
+
|
7
|
+
extern VALUE bird_cTimer;
|
8
|
+
|
9
|
+
struct bird_timer_data
|
10
|
+
{
|
11
|
+
Uint32 frame_start;
|
12
|
+
Uint32 max_frame_duration_ms; // in miliseconds
|
13
|
+
double max_frame_duration_sec; // in seconds
|
14
|
+
};
|
15
|
+
|
16
|
+
VALUE
|
17
|
+
bird_cTimer_initialize(VALUE self, VALUE max_fps);
|
18
|
+
|
19
|
+
VALUE
|
20
|
+
bird_cTimer_tick(VALUE self);
|
21
|
+
|
22
|
+
void
|
23
|
+
Init_red_bird_timer(void);
|
24
|
+
|
25
|
+
#endif // RED_BIRD_TIMER_H
|
data/lib/red_bird.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
require_relative "red_bird/red_bird"
|
3
|
+
require_relative "red_bird/controller"
|
4
|
+
require_relative "red_bird/dynamic_sprite"
|
5
|
+
require_relative "red_bird/engine"
|
6
|
+
require_relative "red_bird/entity"
|
7
|
+
require_relative "red_bird/input_device"
|
8
|
+
require_relative "red_bird/palette"
|
9
|
+
require_relative "red_bird/sprite"
|
10
|
+
require_relative "red_bird/version"
|
11
|
+
|
12
|
+
module RedBird
|
13
|
+
class Error < StandardError; end
|
14
|
+
# Your code goes here...
|
15
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# This module contains different kinds of animations.
|
4
|
+
module Animation
|
5
|
+
# Each frame has a sprite and a duration.
|
6
|
+
#
|
7
|
+
# @!attribute [r] duration
|
8
|
+
# @return [Integer] how long this frame lasts.
|
9
|
+
# @!attribute [r] sprite
|
10
|
+
# @return [RedBird::Sprite] sprite to display in this frame.
|
11
|
+
# @see RedBird::Animation::Base
|
12
|
+
# @author Frederico Linhares
|
13
|
+
class Frame
|
14
|
+
attr_reader :duration, :sprite
|
15
|
+
|
16
|
+
# @param duration [Integer] this animation frame will persist for as many
|
17
|
+
# game frames as defined by this value.
|
18
|
+
# @param sprite [RedBird::Sprite] sprite to be displayed in this frame.
|
19
|
+
# @author Frederico Linhares
|
20
|
+
def initialize(duration, sprite)
|
21
|
+
@duration = duration
|
22
|
+
@sprite = sprite
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create an array of frames and return it. This method must receive an
|
26
|
+
# Array.
|
27
|
+
#
|
28
|
+
# @param frames [Array] each object in it must be an array containing the
|
29
|
+
# attributes for each frame returned.
|
30
|
+
# @author Frederico Linhares
|
31
|
+
def self.sequence(frames)
|
32
|
+
return frames.collect { |i| Frame.new(i[0], i[1]) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Base class for different animations. An animation consists of a sequence
|
37
|
+
# of frames, a state to define which one to show and which comes next and
|
38
|
+
# an animation method to determine how the frames change.
|
39
|
+
#
|
40
|
+
# @author Frederico Linhares
|
41
|
+
class Base
|
42
|
+
# @return [RedBird::Sprite] the sprite for the current stage of the
|
43
|
+
# animation.
|
44
|
+
attr_reader :current_sprite
|
45
|
+
|
46
|
+
# @param frames [Array] an array of RedBird::Frame.
|
47
|
+
# @author Frederico Linhares
|
48
|
+
def initialize(frames)
|
49
|
+
@frames = frames
|
50
|
+
self.set
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set this animation to the initial state.
|
54
|
+
#
|
55
|
+
# @author Frederico Linhares
|
56
|
+
def reset
|
57
|
+
self.set
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# This animation starts in the first frame and goes through every frame
|
62
|
+
# until the last, then it goes back to the first frame and starts again.
|
63
|
+
#
|
64
|
+
# @author Frederico Linhares
|
65
|
+
class Loop < Base
|
66
|
+
# This method must be called for every tick. It changes the current
|
67
|
+
# displayed sprite.
|
68
|
+
def animate
|
69
|
+
@current_time += 1
|
70
|
+
if @current_time > @frames[@current_frame].duration then
|
71
|
+
@current_time -= @frames[@current_frame].duration
|
72
|
+
@current_frame += 1
|
73
|
+
if @current_frame >= @frames.size then
|
74
|
+
@current_frame = 0
|
75
|
+
end
|
76
|
+
@current_sprite = @frames[@current_frame].sprite
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def set
|
82
|
+
@current_time = 0
|
83
|
+
@current_frame = 0
|
84
|
+
@current_sprite = @frames[@current_frame].sprite
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# This animation starts in the first frame and goes through every frame
|
89
|
+
# until the last; then, it moves backward through every frame and starts
|
90
|
+
# again.
|
91
|
+
#
|
92
|
+
# @author Frederico Linhares
|
93
|
+
class BackAndForth < Base
|
94
|
+
# This method must be called for every tick. It changes the current
|
95
|
+
# displayed sprite.
|
96
|
+
def animate
|
97
|
+
case @direction
|
98
|
+
when :forward
|
99
|
+
@current_time += 1
|
100
|
+
if @current_time > @frames[@current_frame].duration then
|
101
|
+
@current_time -= @frames[@current_frame].duration
|
102
|
+
@current_frame += 1
|
103
|
+
if @current_frame >= @frames.size then
|
104
|
+
@direction = :backward
|
105
|
+
@current_frame = @frames.size - 2
|
106
|
+
end
|
107
|
+
@current_sprite = @frames[@current_frame].sprite
|
108
|
+
end
|
109
|
+
when :backward
|
110
|
+
@current_time += 1
|
111
|
+
if @current_time > @frames[@current_frame].duration then
|
112
|
+
@current_time -= @frames[@current_frame].duration
|
113
|
+
@current_frame -= 1
|
114
|
+
if @current_frame < 0 then
|
115
|
+
@direction = :forward
|
116
|
+
@current_frame = 1
|
117
|
+
end
|
118
|
+
@current_sprite = @frames[@current_frame].sprite
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
def set
|
125
|
+
@direction = :forward
|
126
|
+
@current_time = 0
|
127
|
+
@current_frame = 0
|
128
|
+
@current_sprite = @frames[@current_frame].sprite
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# Because most of the time the tilemap used as the scenario is larger than
|
4
|
+
# the screen, you need to choose an entity to use as a reference. A Camera
|
5
|
+
# control the scenario offset and makes the position of the scene rendered in
|
6
|
+
# the screen be relative to an entity.
|
7
|
+
#
|
8
|
+
# @author Frederico Linhares
|
9
|
+
class Camera
|
10
|
+
|
11
|
+
# @param focus [RedBird::RelativeEntity] entity to use as reference for the
|
12
|
+
# scenario
|
13
|
+
# @param scenario [RedBird::TileMap]
|
14
|
+
# @author Frederico Linhares
|
15
|
+
def initialize(focus, scenario)
|
16
|
+
@scenario = scenario
|
17
|
+
@max_hor_offset = @scenario.total_width - @scenario.width
|
18
|
+
@max_ver_offset = @scenario.total_height - @scenario.height
|
19
|
+
self.focus = focus
|
20
|
+
end
|
21
|
+
|
22
|
+
# Change the camere focus.
|
23
|
+
#
|
24
|
+
# @param focus [RedBird::RelativeEntity]
|
25
|
+
# @author Frederico Linhares
|
26
|
+
def focus=(focus)
|
27
|
+
@focus = focus
|
28
|
+
set_center
|
29
|
+
end
|
30
|
+
|
31
|
+
# Updates scenario offset according to entity position.
|
32
|
+
#
|
33
|
+
# @author Frederico Linhares
|
34
|
+
def call
|
35
|
+
hor_offset = (@focus.relative_pos_x - @hor_center).to_i
|
36
|
+
if hor_offset < 0 then
|
37
|
+
@scenario.hor_offset = 0
|
38
|
+
elsif hor_offset > @max_hor_offset then
|
39
|
+
@scenario.hor_offset = @max_hor_offset
|
40
|
+
else
|
41
|
+
@scenario.hor_offset = hor_offset
|
42
|
+
end
|
43
|
+
|
44
|
+
ver_offset = (@focus.relative_pos_y - @ver_center).to_i
|
45
|
+
if ver_offset < 0 then
|
46
|
+
@scenario.ver_offset = 0
|
47
|
+
elsif ver_offset > @max_ver_offset then
|
48
|
+
@scenario.ver_offset = @max_ver_offset
|
49
|
+
else
|
50
|
+
@scenario.ver_offset = ver_offset
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def set_center
|
57
|
+
@hor_center = @scenario.width / 2 - @focus.width / 2
|
58
|
+
@ver_center = @scenario.height / 2 - @focus.height / 2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|