red_bird 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,44 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# A Controller receives commands from a {RedBird::InputDevice} and executes
|
4
|
+
# codes associated with those commands. Every {RedBird::InputDevice} instance
|
5
|
+
# has a variable called +@controller+ that must point to an instance of this
|
6
|
+
# class.
|
7
|
+
#
|
8
|
+
# The purpose of this class is to allow you to change the effect of user
|
9
|
+
# input by merely changing the currently active controller; you would do so,
|
10
|
+
# for example, when the user pauses the game, or when the user's character
|
11
|
+
# enters a vehicle.
|
12
|
+
#
|
13
|
+
# @see RedBird::InputDevice
|
14
|
+
# @author Frederico Linhares
|
15
|
+
class Controller
|
16
|
+
def initialize
|
17
|
+
@commands = {}
|
18
|
+
|
19
|
+
# By default, quit game when window is closed or another signal to end
|
20
|
+
# the game is given.
|
21
|
+
self.add_command(:quit_game) do
|
22
|
+
Engine::quit_game
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use this method to create an association between a command and a code
|
27
|
+
# block.
|
28
|
+
#
|
29
|
+
# @param command [Symbol]
|
30
|
+
# @param function
|
31
|
+
# @author Frederico Linhares
|
32
|
+
def add_command(command, &function)
|
33
|
+
@commands[command] = function
|
34
|
+
end
|
35
|
+
|
36
|
+
# Execute a code block associated with a command.
|
37
|
+
#
|
38
|
+
# @param command [Symbol]
|
39
|
+
# @author Frederico Linhares
|
40
|
+
def call_command(command)
|
41
|
+
@commands[command].call unless @commands[command].nil?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# A sprite is rectangular part of an texture.
|
4
|
+
class DynamicSprite
|
5
|
+
# Create an array of sprites and return it. The array has homogeneous
|
6
|
+
# sprites that do not overlap and have no gaps between them.
|
7
|
+
#
|
8
|
+
# To create a grid starting from the position x 20, y 20, containing three
|
9
|
+
# columns and two rows, each sprite, having 100x80 pixels:
|
10
|
+
#
|
11
|
+
# RedBird::Texture.grid sprite, 20, 20, 3, 2, 100, 80
|
12
|
+
#
|
13
|
+
# @param texture [RedBird::Texture] a texture that will be used as the
|
14
|
+
# source for all sprites.
|
15
|
+
# @param init_x [Integer] initial x coordinate for the grid.
|
16
|
+
# @param init_y [Integer] initial y coordinate for the grid.
|
17
|
+
# @param num_hor_sprites [Integer] number of columns the grid will have.
|
18
|
+
# @param num_ver_sprites [Integer] number of rows the grid will have.
|
19
|
+
# @param width [Integer] width of every sprite.
|
20
|
+
# @param height [Integer] height of every sprite.
|
21
|
+
# @param mods [Hash]
|
22
|
+
# @return [Array]
|
23
|
+
# @author Frederico Linhares
|
24
|
+
def self.grid(texture, init_x, init_y, num_hor_sprites, num_ver_sprites,
|
25
|
+
width, height, mods)
|
26
|
+
sprites = []
|
27
|
+
num_ver_sprites.times do |v|
|
28
|
+
num_hor_sprites.times do |h|
|
29
|
+
sprites << new(
|
30
|
+
texture, init_x + h * width, init_y + v * height, width, height,
|
31
|
+
mods)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
return sprites
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
module Engine
|
4
|
+
# Maximum frames per second. The Engine will try to maintain the game
|
5
|
+
# running at the frame rate defined by this variable.
|
6
|
+
@@max_fps = 30
|
7
|
+
|
8
|
+
# Set frames per second.
|
9
|
+
#
|
10
|
+
# @param fps [Integer]
|
11
|
+
# @author Frederico Linhares
|
12
|
+
def self.fps=(fps)
|
13
|
+
@@max_fps = fps
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get frames per second.
|
17
|
+
#
|
18
|
+
# @return [Integer]frames per second.
|
19
|
+
def self.fps
|
20
|
+
return @@max_fps
|
21
|
+
end
|
22
|
+
|
23
|
+
# Exit the current stage and end the {Engine#run} loop.
|
24
|
+
#
|
25
|
+
# @author Frederico Linhares
|
26
|
+
def self.quit_game
|
27
|
+
@@quit_game = true
|
28
|
+
@@quit_stage = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# Exit the current stage. The code block sent to {Engine#run} will be
|
32
|
+
# called again to get the next stage.
|
33
|
+
#
|
34
|
+
# @author Frederico Linhares
|
35
|
+
def self.quit_stage
|
36
|
+
@@quit_stage = true
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method initializes the engine, executes the main loop, and returns
|
40
|
+
# when the game is over. This function calls the code block to get the
|
41
|
+
# first stage of the game and call it again every time a stage end to get
|
42
|
+
# the next one.
|
43
|
+
#
|
44
|
+
# @param global_data [Object] any object that you want to share between the
|
45
|
+
# game. This method sends this object back to the code block received.
|
46
|
+
# @yield [global_data] give +global_data+ to the block and expect it to
|
47
|
+
# return the next stage.
|
48
|
+
def self.run(global_data, &stage_manager)
|
49
|
+
@@quit_game = false
|
50
|
+
|
51
|
+
self.load do
|
52
|
+
while not @@quit_game do
|
53
|
+
@@quit_stage = false
|
54
|
+
|
55
|
+
timer = Timer.new(@@max_fps)
|
56
|
+
clear_color = Color.new(0, 0, 0, 0)
|
57
|
+
current_stage = stage_manager.call(global_data)
|
58
|
+
|
59
|
+
# The best moment to cleanup memory is before the engine loads a new
|
60
|
+
# stage.
|
61
|
+
GC.start
|
62
|
+
|
63
|
+
while not @@quit_stage do
|
64
|
+
timer.tick
|
65
|
+
|
66
|
+
current_stage.input_device.exec_events(current_stage.entities)
|
67
|
+
|
68
|
+
current_stage.pre_tick
|
69
|
+
current_stage.entities.each { |e| e.tick }
|
70
|
+
current_stage.post_tick
|
71
|
+
|
72
|
+
Render.set_color(clear_color)
|
73
|
+
Render.clear_screen
|
74
|
+
current_stage.entities.each { |e| e.render }
|
75
|
+
Render.update_screen
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# An entity is an object that is managed by the Engine; it has a size, a
|
4
|
+
# position, ticks, and is renderable. {RedBird::Engine.run} expects every
|
5
|
+
# single entity that it receives from {RedBird::Stage} to have these methods
|
6
|
+
# and attributes.
|
7
|
+
#
|
8
|
+
# @author Frederico Linhares
|
9
|
+
class Entity
|
10
|
+
# Position
|
11
|
+
#
|
12
|
+
# @return [Integer]
|
13
|
+
attr_reader :pos_x, :pos_y
|
14
|
+
# Size
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
attr_reader :width, :height
|
18
|
+
# Entities with higher priorit are called first when the
|
19
|
+
# {RedBird::Engine.run} calls {#tick} and {#render}.
|
20
|
+
#
|
21
|
+
# @return [Integer]
|
22
|
+
attr_reader :priority
|
23
|
+
|
24
|
+
# The {RedBird::Engine.run} calls this method every single frame. Overwrite
|
25
|
+
# it if you want your entity to do something every frame.
|
26
|
+
#
|
27
|
+
# @author Frederico Linhares
|
28
|
+
def tick
|
29
|
+
# By default do nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
# The {RedBird::Engine.run} calls this method whenever the cursor is over
|
33
|
+
# this entity. Overwrite this function if you want your entity to react to a
|
34
|
+
# cursor hover.
|
35
|
+
#
|
36
|
+
# @param x [Integer] cursor position.
|
37
|
+
# @param y [Integer] cursor position.
|
38
|
+
# @author Frederico Linhares
|
39
|
+
def cursor_hover(x, y)
|
40
|
+
# By default do nothing
|
41
|
+
end
|
42
|
+
|
43
|
+
# The {RedBird::Engine.run} calls this method whenever there is a click over
|
44
|
+
# this entity. Overwrite this function if you want your entity to react to a
|
45
|
+
# click.
|
46
|
+
#
|
47
|
+
# @param x [Integer] cursor position.
|
48
|
+
# @param y [Integer] cursor position.
|
49
|
+
# @author Frederico Linhares
|
50
|
+
def cursor_down(x, y)
|
51
|
+
# By default do nothing
|
52
|
+
end
|
53
|
+
|
54
|
+
# The {RedBird::Engine.run} calls this method whenever there is a cursor
|
55
|
+
# release over this entity. Overwrite this function if you want your entity
|
56
|
+
# to react to a cursor release.
|
57
|
+
#
|
58
|
+
# @param x [Integer] cursor position.
|
59
|
+
# @param y [Integer] cursor position.
|
60
|
+
# @author Frederico Linhares
|
61
|
+
def cursor_up(x, y)
|
62
|
+
# By default do nothing
|
63
|
+
end
|
64
|
+
|
65
|
+
# The {RedBird::Engine.run} calls this method to render this entity into
|
66
|
+
# the scree. Overwrite this method if you want to change how the entity
|
67
|
+
# renders into the screen.
|
68
|
+
#
|
69
|
+
# @author Frederico Linhares
|
70
|
+
def render
|
71
|
+
self.current_sprite.render_to_screen(self.pos_x, self.pos_y)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# This class does collision tests between entities.
|
4
|
+
class EntityCollision
|
5
|
+
# @param entities [Array<RedBird::Entity>] array containing the entities to
|
6
|
+
# test for collusion.
|
7
|
+
# @author Frederico Linhares
|
8
|
+
def initialize(entities)
|
9
|
+
@entities = entities
|
10
|
+
end
|
11
|
+
|
12
|
+
# Loop over entities in the array and test if they collide. In the case of
|
13
|
+
# collision, it calls the method +collide+ of the entity and sends the
|
14
|
+
# entity it collided to as a single argument.
|
15
|
+
#
|
16
|
+
# @author Frederico Linhares
|
17
|
+
def call
|
18
|
+
# Calculate entities rote
|
19
|
+
@entities.each_with_index do |e1, i|
|
20
|
+
@entities.drop(i + 1).each do |e2|
|
21
|
+
if e1.collision_box.collide? e2.collision_box then
|
22
|
+
e1.collide(e2)
|
23
|
+
e2.collide(e1)
|
24
|
+
# If an object responds to 'collision_box' then it is tested.
|
25
|
+
end if e1.respond_to?('collision_box')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
module RedBird
|
3
|
+
# The InputDevice receives input from the keyboard, joystick, and mouse;
|
4
|
+
# then, it translates the input into a meaningful command name and sends it
|
5
|
+
# to a Controller. For example, you can use it to translate the 'space' key
|
6
|
+
# from the keyboard and an 'x' button from a controller into a +:jump+
|
7
|
+
# command.
|
8
|
+
#
|
9
|
+
# This class has an internal Hash that translates inputs into commands. For
|
10
|
+
# every input, it looks if there is a key associated with that input; it
|
11
|
+
# sends a command if such key exists; otherwise, it does nothing.
|
12
|
+
#
|
13
|
+
# @see RedBird::Controller
|
14
|
+
# @author Frederico Linhares
|
15
|
+
class InputDevice
|
16
|
+
# Defines a {RedBird::Controller} to receives commands from the input.
|
17
|
+
attr_writer :controller
|
18
|
+
|
19
|
+
# @param controller [RedBird::Controller]
|
20
|
+
# @author Frederico Linhares
|
21
|
+
def initialize(controller)
|
22
|
+
@controller = controller
|
23
|
+
@keydown_inputs = {}
|
24
|
+
@keyup_inputs = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Associates a keydown even to a command_name.
|
28
|
+
#
|
29
|
+
# @param keycode [Integer] use constants under {RedBird::Keycode}.
|
30
|
+
# @param command_name [Symbol]
|
31
|
+
# @author Frederico Linhares
|
32
|
+
def add_keydown(keycode, command_name)
|
33
|
+
@keydown_inputs[keycode] = command_name
|
34
|
+
end
|
35
|
+
|
36
|
+
# Associates a keyup even to a command_name.
|
37
|
+
#
|
38
|
+
# @param keycode [Integer] use constants under {RedBird::Keycode}.
|
39
|
+
# @param command_name [Symbol]
|
40
|
+
# @author Frederico Linhares
|
41
|
+
def add_keyup(keycode, command_name)
|
42
|
+
@keyup_inputs[keycode] = command_name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gets all events that are in the queue, translate then into commands, and
|
46
|
+
# send then to the current controller.
|
47
|
+
#
|
48
|
+
# @param entities [Array<RedBird::Entity>] entities that can receive events
|
49
|
+
# from mouse.
|
50
|
+
# @author Frederico Linhares
|
51
|
+
def exec_events(entities)
|
52
|
+
get_events do |type, data|
|
53
|
+
case type
|
54
|
+
when :key_down then
|
55
|
+
@controller.call_command(@keydown_inputs[data])
|
56
|
+
when :key_up then
|
57
|
+
@controller.call_command(@keyup_inputs[data])
|
58
|
+
when :mouse_motion then
|
59
|
+
entity = cursor_hover_entity(entities, data[:x], data[:y])
|
60
|
+
entity.cursor_hover(data[:x], data[:y]) unless entity.nil?
|
61
|
+
when :mouse_button_down then
|
62
|
+
entity = cursor_hover_entity(entities, data[:x], data[:y])
|
63
|
+
entity.cursor_down(data[:x], data[:y]) unless entity.nil?
|
64
|
+
when :mouse_button_up then
|
65
|
+
entity = cursor_hover_entity(entities, data[:x], data[:y])
|
66
|
+
entity.cursor_up(data[:x], data[:y]) unless entity.nil?
|
67
|
+
when :quit_game then
|
68
|
+
Engine::quit_game
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
# This function is called when user move the mouse to se if mouse is hover
|
75
|
+
# an entity.
|
76
|
+
def cursor_hover_entity(entities, x, y)
|
77
|
+
entities.each do |e|
|
78
|
+
if(x >= e.pos_x and x <= e.pos_x + e.width and
|
79
|
+
y >= e.pos_y and y <= e.pos_y + e.height) then
|
80
|
+
return e
|
81
|
+
end
|
82
|
+
end
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module RedBird
|
5
|
+
class Palette
|
6
|
+
# Load information from a YAML file and uses it to create an instance of this
|
7
|
+
# class
|
8
|
+
#
|
9
|
+
# @param tile_file [String] path to the yaml file.
|
10
|
+
# @return [RedBird::Palette]
|
11
|
+
# @author Frederico Linhares
|
12
|
+
def self.from_yml(palette_file)
|
13
|
+
data = YAML.load_file(palette_file)
|
14
|
+
|
15
|
+
# Convert data from YAML into Color instances.
|
16
|
+
colors = data.map do |i|
|
17
|
+
Color.new(i[0], i[1], i[2], i[3])
|
18
|
+
end
|
19
|
+
|
20
|
+
return self.new(colors)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
require_relative 'entity'
|
3
|
+
|
4
|
+
module RedBird
|
5
|
+
# The {RedBird::Entity} has an absolute position on the screen. Use this
|
6
|
+
# class if you want an entity that has a position relative to a scenario
|
7
|
+
# ({RedBird::TileMap}).
|
8
|
+
#
|
9
|
+
# @author Frederico Linhares
|
10
|
+
class RelativeEntity < Entity
|
11
|
+
# @return [RedBird::Rect]
|
12
|
+
attr_reader :collision_box
|
13
|
+
|
14
|
+
# @param relative_pos_x [Integer] position in the scenario
|
15
|
+
# @param relative_pos_y [Integer] position in the scenario
|
16
|
+
# @param width [Integer]
|
17
|
+
# @param height [Integer]
|
18
|
+
# @author Frederico Linhares
|
19
|
+
def initialize(relative_pos_x, relative_pos_y, width, height)
|
20
|
+
super()
|
21
|
+
|
22
|
+
@priority = 1
|
23
|
+
|
24
|
+
@collision_box = RedBird::Rect.new(
|
25
|
+
relative_pos_x, relative_pos_y, width, height)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define which scenario to use as a reference for every
|
29
|
+
# {RedBird::RelativeEntity}.
|
30
|
+
#
|
31
|
+
# @param scenario [RedBird::TileMap]
|
32
|
+
# @author Frederico Linhares
|
33
|
+
def self.scenario=(scenario)
|
34
|
+
@@scenario = scenario
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [RedBird::TileMap]
|
38
|
+
# @author Frederico Linhares
|
39
|
+
def self.scenario
|
40
|
+
return @@scenario
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Integer] position in the scenario
|
44
|
+
# @author Frederico Linhares
|
45
|
+
def relative_pos_x
|
46
|
+
return @collision_box.x
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Integer] position in the scenario
|
50
|
+
# @author Frederico Linhares
|
51
|
+
def relative_pos_y
|
52
|
+
return @collision_box.y
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param x [Integer] position in the scenario
|
56
|
+
# @author Frederico Linhares
|
57
|
+
def relative_pos_x=(x)
|
58
|
+
@collision_box.x = x
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param y [Integer] position in the scenario
|
62
|
+
# @author Frederico Linhares
|
63
|
+
def relative_pos_y=(y)
|
64
|
+
@collision_box.y = y
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gets absolute position (position in the screen).
|
68
|
+
#
|
69
|
+
# @return [Float] position in the screen
|
70
|
+
# @author Frederico Linhares
|
71
|
+
def pos_x
|
72
|
+
@collision_box.x - @@scenario.hor_offset + @@scenario.pos_x
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gets absolute position (position in the screen).
|
76
|
+
#
|
77
|
+
# @return [Float] position in the screen
|
78
|
+
# @author Frederico Linhares
|
79
|
+
def pos_y
|
80
|
+
@collision_box.y - @@scenario.ver_offset + @@scenario.pos_y
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Integer]
|
84
|
+
# @author Frederico Linhares
|
85
|
+
def width
|
86
|
+
@collision_box.width
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Integer]
|
90
|
+
# @author Frederico Linhares
|
91
|
+
def height
|
92
|
+
@collision_box.height
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|