fantasy 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 98740063fd63398988bf5b11e625a75e2526f617ab22e9aa1ff483a65c0f91cb
4
+ data.tar.gz: b4eecd987e01f24f2a1d3aef7627c195992ec0ffa0c4b124fb6ca577896a2a58
5
+ SHA512:
6
+ metadata.gz: ab99759553de96490612fbf1c6089db1113ec26969876b88d9215fbb1317a7ee08dfef98cd376c7843750013ed3981b10fc0beba0e816a6c5c7358511166b023
7
+ data.tar.gz: 69c41125931104d3440c02bfe1073b6d37f27d2efebbe3fdd331165c014739bf81f92d80c83c46f636d0eda77d583a802a7f377c943ce8964a8bf9d7567f7d4d
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-03-11
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ ruby ">= 3.0.0"
6
+
7
+ # Specify your gem's dependencies in fantasy.gemspec
8
+ gemspec
9
+
10
+ gem "rake", "~> 13.0"
11
+ gem "minitest", "~> 5.0"
12
+ gem "rubocop", "~> 1.21"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Fernando Guillen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Ruby in Fantasy
2
+
3
+ An upper layer over Gosu library to offer a more friendly API for an easy and lean game development.
4
+
5
+ Specially intended to use Ruby as a learning language to introduce children into programming.
6
+
7
+ **Attention**: This project is in early development phase, right now it is just an experiment
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem "fantasy"
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install fantasy
24
+
25
+ ## Features
26
+
27
+ ### Actor
28
+
29
+ Managing game elements which have (optionally) image, movement and collision
30
+
31
+ - Easy to set an `image`
32
+ - Managing movement through `direction` and `speed`
33
+ - Built-in movement control through cursors
34
+ - Collision detection, OnCollision callback
35
+ - Jump (TODO)
36
+ - Gravity (TODO)
37
+ - Animations (TODO)
38
+ - Possibility to extend Actor class or instantiate it directly for simple characters
39
+
40
+ ### Clock
41
+
42
+ Create new execution threads to program delayed behaviors, repetitive actions, animations, ...
43
+
44
+ - Run now
45
+ - Run after a delay
46
+ - Repeat
47
+ - Stop
48
+ - Callback when finished (TODO)
49
+
50
+ ### Animations
51
+
52
+ Add animations to an Actor and activate them when special state triggered
53
+
54
+ - Set frames (TODO)
55
+ - Duration in seconds (TODO)
56
+ - Run once (TODO)
57
+ - Run on loop (TODO)
58
+ - Stop (TODO)
59
+ - Callback when finished (TODO)
60
+
61
+ ### UI
62
+
63
+ #### Button (TODO)
64
+
65
+ Easy to set up a graphical component that responds to mouse hover and click events.
66
+
67
+ - Set image (TODO)
68
+ - Set image when hover (TODO)
69
+ - Set image when clicked (TODO)
70
+ - OnClicked callback (TODO)
71
+
72
+ #### HUD Elements
73
+
74
+ For easy creation of head-up display components.
75
+
76
+ - Text Element
77
+ - Image Element
78
+ - Auto update text (observer variable changes) (TODO)
79
+
80
+ ### Debug Mode
81
+
82
+ When active different attributes from all the Actors and HUD elements will be visible in the screen.
83
+
84
+ - Position
85
+ - Collider
86
+
87
+ On top, when debug mode is active, the Actors and HUD elements become draggable.
88
+ The new position won't be persistent but it will be helpful to identify which should be the desired position.
89
+
90
+ ### Camera
91
+
92
+ Setting up a camera component that we can easily move around and all the active
93
+ Actors in the game will be rendered in the relative position to this camera.
94
+
95
+ - Allow camera move
96
+ - Easy to follow one actor
97
+
98
+ ### Colors palette (TODO)
99
+
100
+ - Not deal with RGB or anything, just a list of colors (TODO)
101
+
102
+ ### Game Scene transitions
103
+
104
+ Easy to configure 3 basic game states:
105
+
106
+ - Initial screen
107
+ - Game
108
+ - Game over screen
109
+
110
+ Each state should be independent and unique Actors and other elements can be created and configured for each state
111
+
112
+ Built-in mechanism to move from one state to another.
113
+
114
+ ### Pause Game (TODO)
115
+
116
+ - Pause game, pause Actors, animations, clocks, ... (TODO)
117
+
118
+ ### Core run
119
+
120
+ Move the core functions to the top level hierarchy so I don't need to create a `Game::Window` class
121
+
122
+ ### Sound
123
+
124
+ Direct and easy way to play a sound
125
+
126
+ ### Data Persistance (TODO)
127
+
128
+ Simple mechanismo to save data in disk. For user preferences, game progress, high scores and others
129
+
130
+ ### User Inputs
131
+
132
+ Easy access to keyboard and mouse inputs on any part of the code. Specially in the Actors.
133
+
134
+ - Allow "on_space_bar" and each Actor (TODO)
135
+ - Allow multiple "on_space_bar" (TODO)
136
+ - Remove "on_space_bar" when changing scene (TODO)
137
+
138
+ ## API
139
+
140
+
141
+
142
+ ## Credits for assets
143
+
144
+ - Sprites: www.kenney.nl
145
+ - Font: VT323 Project Authors (peter.hull@oikoi.com)
146
+
147
+ ## Bugs
148
+
149
+ - When dragging in debug mode new elements are being added to the drag (TODO)
150
+
151
+ ## Development
152
+
153
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
154
+
155
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
+
157
+ ## Contributing
158
+
159
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fguillen/fantasy.
160
+
161
+ ## License
162
+
163
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "fantasy"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/fantasy.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/fantasy/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fantasy"
7
+ spec.version = Fantasy::VERSION
8
+ spec.authors = ["Fernando Guillen"]
9
+ spec.email = ["fguillen.mail@gmail.com"]
10
+
11
+ spec.summary = "Simple toolbox library and lean API to build great mini games"
12
+ spec.description = "Simple toolbox library and lean API to build great mini games"
13
+ spec.homepage = "https://github.com/fguillen/fantasy"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/fguillen/fantasy"
21
+ spec.metadata["changelog_uri"] = "https://github.com/fguillen/fantasy/blob/main/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ spec.add_dependency "gosu", "~> 1.4.1"
36
+ spec.add_dependency "vector2d", "~> 2.2.3"
37
+
38
+ # For more information and examples about making a new gem, checkout our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ end
data/fonts/OFL.txt ADDED
@@ -0,0 +1,93 @@
1
+ Copyright 2011, The VT323 Project Authors (peter.hull@oikoi.com)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file
@@ -0,0 +1,141 @@
1
+ class Actor
2
+ attr_reader :image, :moving_with_cursors
3
+ attr_accessor :position, :direction, :speed, :solid, :scale, :name, :layer
4
+
5
+ def initialize(image_name)
6
+ @image = Gosu::Image.new("#{__dir__}/../images/#{image_name}.png", { retro: true })
7
+ @name = image_name
8
+ @position = Coordinates.new(0, 0)
9
+ @direction = Coordinates.new(0, 0)
10
+ @speed = 0
11
+ @scale = 1
12
+ @on_after_move_callback = nil
13
+ @moving_with_cursors = false
14
+ @solid = false
15
+ @draggable_on_debug = true
16
+ @dragging = false
17
+ @dragging_offset = nil
18
+ @layer = 0
19
+
20
+ @on_collision_callback = nil
21
+
22
+ Global.actors << self
23
+ end
24
+
25
+ def image=(image_name)
26
+ @image = Gosu::Image.new("#{__dir__}/../images/#{image_name}.png", { retro: true })
27
+ end
28
+
29
+ def width
30
+ @image.width() * @scale
31
+ end
32
+
33
+ def height
34
+ @image.height() * @scale
35
+ end
36
+
37
+ def move_with_cursors
38
+ @moving_with_cursors = true
39
+ end
40
+
41
+ def direction=(value)
42
+ @direction = value
43
+ @direction = @direction.normalize
44
+ end
45
+
46
+ def draw
47
+ @image.draw(position_in_camera.x, position_in_camera.y, 0, @scale, @scale)
48
+
49
+ draw_debug if Global.debug
50
+ end
51
+
52
+ def draw_debug
53
+ Utils.draw_frame(position_in_camera.x, position_in_camera.y, width, height, 1, Gosu::Color::RED) if solid
54
+ Global.pixel_font.draw_text("#{@position.x.floor},#{@position.y.floor}", position_in_camera.x, position_in_camera.y - 20, 1)
55
+ end
56
+
57
+ def position_in_camera
58
+ @position - Global.camera.position
59
+ end
60
+
61
+ def move
62
+ mouse_position = Global.mouse_position + Global.camera.position
63
+
64
+ if @draggable_on_debug && Global.debug && !@dragging && Gosu.button_down?(Gosu::MS_LEFT) && Utils.collision_at?(self, mouse_position.x, mouse_position.y)
65
+ @dragging = true
66
+ @dragging_offset = mouse_position - @position
67
+ end
68
+
69
+ if @dragging && !Gosu.button_down?(Gosu::MS_LEFT)
70
+ @dragging = false
71
+ end
72
+
73
+ if @dragging
74
+ @position = mouse_position - @dragging_offset
75
+ else
76
+ calculate_direction_by_cursors if @moving_with_cursors
77
+
78
+ if @direction != Coordinates.zero && !@speed.zero?
79
+ @last_position = @position
80
+ @position = @position + (@direction * @speed * Global.frame_time)
81
+
82
+ if solid?
83
+ actual_collisions = collisions
84
+ actual_collisions.each do |actor|
85
+ collision_with(actor)
86
+ actor.collision_with(self)
87
+ end
88
+
89
+ @position = @last_position if actual_collisions.any?
90
+ end
91
+ end
92
+ end
93
+
94
+ @on_after_move_callback.call unless @on_after_move_callback.nil?
95
+ end
96
+
97
+ def solid?
98
+ @solid
99
+ end
100
+
101
+ def on_after_move(&block)
102
+ @on_after_move_callback = block
103
+ end
104
+
105
+ def on_collision(&block)
106
+ @on_collision_callback = block
107
+ end
108
+
109
+ def on_destroy(&block)
110
+ @on_destroy_callback = block
111
+ end
112
+
113
+ def collision_with(actor)
114
+ @on_collision_callback.call(actor) unless @on_collision_callback.nil?
115
+ end
116
+
117
+ def calculate_direction_by_cursors
118
+ if Gosu.button_down?(Gosu::KB_DOWN)
119
+ @direction = Coordinates.down
120
+ elsif Gosu.button_down?(Gosu::KB_UP)
121
+ @direction = Coordinates.up
122
+ elsif Gosu.button_down?(Gosu::KB_RIGHT)
123
+ @direction = Coordinates.right
124
+ elsif Gosu.button_down?(Gosu::KB_LEFT)
125
+ @direction = Coordinates.left
126
+ else
127
+ @direction = Coordinates.zero
128
+ end
129
+ end
130
+
131
+ def collisions
132
+ Global.actors.reject { |e| e == self }.select { |e| e.solid? }.select do |actor|
133
+ Utils.collision? self, actor
134
+ end
135
+ end
136
+
137
+ def destroy
138
+ @on_destroy_callback.call unless @on_destroy_callback.nil?
139
+ Global.actors.delete(self)
140
+ end
141
+ end
@@ -0,0 +1,59 @@
1
+ Global.setup_proc = nil
2
+ Global.loop_proc = nil
3
+
4
+ def on_presentation(&block)
5
+ Global.presentation_proc = block
6
+ end
7
+
8
+ def on_game(&block)
9
+ Global.game_proc = block
10
+ end
11
+
12
+ def on_end(&block)
13
+ Global.end_proc = block
14
+ end
15
+
16
+ def on_setup(&block)
17
+ Global.setup_proc = block
18
+ end
19
+
20
+ def on_loop(&block)
21
+ Global.loop_proc = block
22
+ end
23
+
24
+ def on_button(&block)
25
+ Global.button_proc = block
26
+ end
27
+
28
+ def on_space_bar(&block)
29
+ Global.space_bar_proc = block
30
+ end
31
+
32
+ def on_cursor_up(&block)
33
+ Global.cursor_up_proc = block
34
+ end
35
+
36
+ def on_cursor_down(&block)
37
+ Global.cursor_down_proc = block
38
+ end
39
+
40
+ def on_cursor_left(&block)
41
+ Global.cursor_left_proc = block
42
+ end
43
+
44
+ def on_cursor_right(&block)
45
+ Global.cursor_right_proc = block
46
+ end
47
+
48
+ def on_mouse_button_left(&block)
49
+ Global.mouse_button_left_proc = block
50
+ end
51
+
52
+ def on_mouse_button_right(&block)
53
+ Global.mouse_button_right_proc = block
54
+ end
55
+
56
+ def start!
57
+ Global.game = Game.new
58
+ Global.game.show
59
+ end
@@ -0,0 +1,7 @@
1
+ class Camera
2
+ attr_accessor :position
3
+
4
+ def initialize(position: Coordinates.zero)
5
+ @position = position
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ class Clock
2
+ def initialize(&block)
3
+ @block = block
4
+ @thread = nil
5
+
6
+ Global.clocks << self
7
+ end
8
+
9
+ def run_now
10
+ @thread =
11
+ Thread.new do
12
+ @block.call
13
+ end
14
+ end
15
+
16
+ def run_on(seconds:)
17
+ @thread =
18
+ Thread.new do
19
+ sleep(seconds)
20
+ @block.call
21
+ end
22
+ end
23
+
24
+ def repeat(seconds: 1, times: Float::INFINITY)
25
+ times_executed = 0
26
+ @thread =
27
+ Thread.new do
28
+ while(times_executed < times)
29
+ @block.call
30
+ times_executed += 1;
31
+ sleep(seconds)
32
+ end
33
+ end
34
+ end
35
+
36
+ def stop
37
+ Thread.kill(@thread)
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ class Color < Gosu::Color
2
+ def initialize(r:, g:, b:, a: 255)
3
+ super(a, r, g, b)
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ require "vector2d"
2
+
3
+ class Coordinates < Vector2d
4
+ def self.zero
5
+ Coordinates.new(0, 0)
6
+ end
7
+
8
+ def self.up
9
+ Coordinates.new(0, -1)
10
+ end
11
+
12
+ def self.down
13
+ Coordinates.new(0, 1)
14
+ end
15
+
16
+ def self.left
17
+ Coordinates.new(-1, 0)
18
+ end
19
+
20
+ def self.right
21
+ Coordinates.new(1, 0)
22
+ end
23
+
24
+ def x=(value)
25
+ @x = value
26
+ end
27
+
28
+ def y=(value)
29
+ @y = value
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module Draggable
2
+ def drag
3
+ puts "XXX: dragging 1 #{name}"
4
+ mouse_position = Global.mouse_position
5
+
6
+ puts "@draggable_on_debug: #{@draggable_on_debug}"
7
+ puts "@dragging: #{@dragging}"
8
+ puts "Gosu.button_down?(Gosu::MS_LEFT): #{Gosu.button_down?(Gosu::MS_LEFT)}"
9
+ puts "collision: #{Utils.collision_at?(self, mouse_position.x, mouse_position.y)}"
10
+
11
+ if @draggable_on_debug && !@dragging && Gosu.button_down?(Gosu::MS_LEFT) && Utils.collision_at?(self, mouse_position.x, mouse_position.y)
12
+ puts "XXX: dragging start #{name}"
13
+ @dragging = true
14
+ @dragging_offset = mouse_position - @position
15
+ end
16
+
17
+ if @dragging && !Gosu.button_down?(Gosu::MS_LEFT)
18
+ puts "XXX: dragging end #{name}"
19
+ @dragging = false
20
+ end
21
+
22
+ if @dragging
23
+ puts "XXX: dragging 3 #{name}"
24
+ @position = mouse_position - @dragging_offset
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,106 @@
1
+ require "ostruct"
2
+
3
+ module Global
4
+ class << self
5
+ attr_accessor :actors, :hud_texts, :hud_images, :clocks
6
+ attr_accessor :debug
7
+ attr_accessor :setup_proc, :loop_proc, :button_proc
8
+ attr_accessor :presentation_proc, :game_proc, :end_proc
9
+
10
+ attr_accessor :space_bar_proc
11
+ attr_accessor :cursor_up_proc, :cursor_down_proc, :cursor_left_proc, :cursor_right_proc
12
+ attr_accessor :mouse_button_left_proc, :mouse_button_right_proc
13
+
14
+ attr_accessor :background
15
+
16
+ attr_accessor :game
17
+ attr_reader :frame_time # delta_time
18
+ attr_reader :pixel_font
19
+ attr_reader :references
20
+ attr_reader :camera
21
+ attr_reader :game_state
22
+
23
+ def initialize
24
+ puts "Global.initialize"
25
+ @actors = []
26
+ @hud_texts = []
27
+ @hud_images = []
28
+ @clocks = []
29
+ @last_frame_at = Time.now
30
+ @debug = false
31
+ @pixel_font = Gosu::Font.new(20, { name: "#{__dir__}/../fonts/VT323-Regular.ttf" } )
32
+ @d_key_pressed = false
33
+ @references = OpenStruct.new
34
+ @camera = Camera.new(position: Coordinates.zero)
35
+ @game_state = Global.presentation_proc.nil? ? "game" : "presentation"
36
+
37
+ @presentation_proc = Global.default_on_presentation if @presentation_proc.nil?
38
+ @game_proc = Global.default_on_game if @game_proc.nil?
39
+ @end_proc = Global.default_on_end if @end_proc.nil?
40
+ @paused = false
41
+ end
42
+
43
+ def update
44
+ @frame_time = Time.now - @last_frame_at
45
+ @last_frame_at = Time.now
46
+
47
+ if Gosu.button_down?(Gosu::KB_D) && !@d_key_pressed
48
+ @debug = !@debug
49
+ @d_key_pressed = true
50
+ end
51
+
52
+ if !Gosu.button_down?(Gosu::KB_D) && @d_key_pressed
53
+ @d_key_pressed = false
54
+ end
55
+ end
56
+
57
+ def add_reference(name:, object:)
58
+ @references[name] = object
59
+ end
60
+
61
+ def default_on_presentation
62
+ Global.go_to_game
63
+ end
64
+
65
+ def default_on_game
66
+ raise "You have to define a 'on_game' block"
67
+ end
68
+
69
+ def default_on_end
70
+ Global.go_to_presentation
71
+ end
72
+
73
+ def go_to_presentation
74
+ puts "Game stage 'presentation'"
75
+
76
+ clear_state_elements
77
+ presentation_proc.call
78
+ end
79
+
80
+ def go_to_game
81
+ puts "Game stage 'game'"
82
+
83
+ clear_state_elements
84
+ game_proc.call
85
+ end
86
+
87
+ def go_to_end
88
+ puts "Game stage 'end'"
89
+
90
+ clear_state_elements
91
+ end_proc.call
92
+ end
93
+
94
+ def clear_state_elements
95
+ @actors.clear
96
+ @hud_texts.clear
97
+ @hud_images.clear
98
+ @clocks.each(&:stop)
99
+ @background = Color.new(r: 0, g: 0, b: 0)
100
+ end
101
+
102
+ def mouse_position
103
+ Coordinates.new(Global.game.mouse_x, Global.game.mouse_y)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,46 @@
1
+ class HudImage
2
+ include Draggable
3
+
4
+ attr_accessor :name, :scale, :color, :visible, :position, :layer
5
+
6
+ def initialize(position:, image_name: )
7
+ @image = Gosu::Image.new("#{__dir__}/../images/#{image_name}.png", { retro: true })
8
+ @name = image_name
9
+ @position = position
10
+ @scale = 1
11
+ @visible = true
12
+ @draggable_on_debug = true
13
+ @dragging = false
14
+ @layer = 100
15
+
16
+ Global.hud_images.push(self)
17
+ end
18
+
19
+ def width
20
+ @image.width() * @scale
21
+ end
22
+
23
+ def height
24
+ @image.height() * @scale
25
+ end
26
+
27
+ def move
28
+ drag if Global.debug
29
+ end
30
+
31
+ def draw
32
+ if visible
33
+ @image.draw(@position.x, @position.y, 0, @scale, @scale)
34
+ end
35
+
36
+ draw_debug if Global.debug
37
+ end
38
+
39
+ def draw_debug
40
+ Global.pixel_font.draw_text("#{@position.x.floor},#{@position.y.floor}", @position.x, @position.y - 20, 1)
41
+ end
42
+
43
+ def destroy
44
+ Global.hud_images.delete(self)
45
+ end
46
+ end
@@ -0,0 +1,57 @@
1
+ class HudText
2
+ attr_accessor :text, :size, :color, :visible, :layer, :in_world
3
+
4
+ def initialize(position:, text: "")
5
+ @position = position
6
+ @text = text
7
+ @size = "medium"
8
+ @color = Gosu::Color::WHITE
9
+ @visible = true
10
+ @layer = 100
11
+ @in_world = false
12
+
13
+ Global.hud_texts.push(self)
14
+ end
15
+
16
+ def move; end
17
+
18
+ def draw
19
+ if visible
20
+ Global.pixel_font.draw_markup(text, screen_position.x + scale, screen_position.y - 20 + scale, 1, scale, scale, Gosu::Color::BLACK)
21
+ Global.pixel_font.draw_markup(text, screen_position.x, screen_position.y - 20, 1, scale, scale, color)
22
+ end
23
+
24
+ draw_debug if Global.debug
25
+ end
26
+
27
+ def scale
28
+ case @size
29
+ when "small"
30
+ 1
31
+ when "medium"
32
+ 2
33
+ when "big"
34
+ 3
35
+ when "huge"
36
+ 4
37
+ else
38
+ raise "HudText.size not valid '#{@size}'. Valid sizes: 'small, medium, big, huge'"
39
+ end
40
+ end
41
+
42
+ def screen_position
43
+ if @in_world
44
+ @position - Global.camera.position
45
+ else
46
+ @position
47
+ end
48
+ end
49
+
50
+ def destroy
51
+ Global.hud_texts.delete(self)
52
+ end
53
+
54
+ def draw_debug
55
+ Global.pixel_font.draw_text("#{@position.x.floor},#{@position.y.floor}", @position.x, @position.y, 1)
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ class Game < Gosu::Window
2
+ def initialize
3
+ # TODO: require SCREEN_WIDTH and SCREEN_HEIGHT
4
+ super(SCREEN_WIDTH, SCREEN_HEIGHT)
5
+ Global.initialize
6
+
7
+ Global.presentation_proc.call()
8
+ end
9
+
10
+ def button_down(button_id)
11
+ case button_id
12
+ when Gosu::KB_DOWN then Global.cursor_down_proc.call unless Global.cursor_down_proc.nil?
13
+ when Gosu::KB_UP then Global.cursor_up_proc.call unless Global.cursor_up_proc.nil?
14
+ when Gosu::KB_LEFT then Global.cursor_left_proc.call unless Global.cursor_left_proc.nil?
15
+ when Gosu::KB_RIGHT then Global.cursor_right_proc.call unless Global.cursor_right_proc.nil?
16
+ when Gosu::MS_LEFT then Global.mouse_button_left_proc.call unless Global.mouse_button_left_proc.nil?
17
+ when Gosu::MS_RIGHT then Global.mouse_button_right_proc.call unless Global.mouse_button_right_proc.nil?
18
+ when Gosu::KB_SPACE then Global.space_bar_proc.call unless Global.space_bar_proc.nil?
19
+ end
20
+
21
+ Global.button_proc.call(button_id) unless Global.button_proc.nil?
22
+
23
+ super
24
+ end
25
+
26
+ def update
27
+ Global.update
28
+
29
+ Global.actors.each do |e|
30
+ e.move
31
+ end
32
+
33
+ Global.hud_texts.each do |e|
34
+ e.move
35
+ end
36
+
37
+ Global.hud_images.each do |e|
38
+ e.move
39
+ end
40
+
41
+ Global.loop_proc.call() unless Global.loop_proc.nil?
42
+ end
43
+
44
+ def draw
45
+ Gosu.draw_rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, Global.background)
46
+
47
+ (
48
+ Global.actors +
49
+ Global.hud_texts +
50
+ Global.hud_images
51
+ ).sort_by(&:layer).each do |e|
52
+ e.draw
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ module Sound
2
+ class << self
3
+ @@sounds = {}
4
+
5
+ def play(sound_name)
6
+ locate_sound(sound_name).play
7
+ end
8
+
9
+ def locate_sound(sound_name)
10
+ return @@sounds[sound_name] if @@sounds[sound_name]
11
+
12
+ puts "Initialize Sound: '#{sound_name}'"
13
+
14
+ base_path = "#{__dir__}/../sounds"
15
+ file_name = Dir.entries(base_path).find { |e| e.start_with?(sound_name) }
16
+ @@sounds[sound_name] = Gosu::Sample.new("#{base_path}/#{file_name}")
17
+
18
+ return @@sounds[sound_name]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Utils
2
+ # https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
3
+ def self.collision?(actor_1, actor_2)
4
+ (
5
+ actor_1.position.x < (actor_2.position.x + actor_2.width) &&
6
+ (actor_1.position.x + actor_1.width) > actor_2.position.x &&
7
+ actor_1.position.y < (actor_2.position.y + actor_2.height) &&
8
+ actor_1.position.y + actor_1.height > actor_2.position.y
9
+ )
10
+ end
11
+
12
+ def self.collision_at?(actor, x, y)
13
+ (
14
+ actor.position.x < x &&
15
+ (actor.position.x + actor.width) > x &&
16
+ actor.position.y < y &&
17
+ actor.position.y + actor.height > y
18
+ )
19
+ end
20
+
21
+ def self.draw_frame(x, y, width, height, stroke, color)
22
+ Gosu.draw_rect(x, y, width, stroke, color)
23
+ Gosu.draw_rect(x + (width - stroke), y, stroke, height, color)
24
+ Gosu.draw_rect(x, y + (height - stroke), width, stroke, color)
25
+ Gosu.draw_rect(x, y, stroke, height, color)
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fantasy
4
+ VERSION = "0.1.0"
5
+ end
data/lib/fantasy.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require "gosu"
3
+
4
+ require_relative "fantasy/version"
5
+ require_relative "fantasy/draggable"
6
+ require_relative "fantasy/color"
7
+ require_relative "fantasy/actor"
8
+ require_relative "fantasy/coordinates"
9
+ require_relative "fantasy/utils"
10
+ require_relative "fantasy/global"
11
+ require_relative "fantasy/clock"
12
+ require_relative "fantasy/loop"
13
+ require_relative "fantasy/hud_text"
14
+ require_relative "fantasy/hud_image"
15
+ require_relative "fantasy/sound"
16
+ require_relative "fantasy/camera"
17
+ require_relative "fantasy/base"
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fantasy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Fernando Guillen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gosu
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: vector2d
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.3
41
+ description: Simple toolbox library and lean API to build great mini games
42
+ email:
43
+ - fguillen.mail@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rubocop.yml"
49
+ - CHANGELOG.md
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/console
55
+ - bin/setup
56
+ - fantasy.gemspec
57
+ - fonts/OFL.txt
58
+ - fonts/VT323-Regular.ttf
59
+ - lib/fantasy.rb
60
+ - lib/fantasy/actor.rb
61
+ - lib/fantasy/base.rb
62
+ - lib/fantasy/camera.rb
63
+ - lib/fantasy/clock.rb
64
+ - lib/fantasy/color.rb
65
+ - lib/fantasy/coordinates.rb
66
+ - lib/fantasy/draggable.rb
67
+ - lib/fantasy/global.rb
68
+ - lib/fantasy/hud_image.rb
69
+ - lib/fantasy/hud_text.rb
70
+ - lib/fantasy/loop.rb
71
+ - lib/fantasy/sound.rb
72
+ - lib/fantasy/utils.rb
73
+ - lib/fantasy/version.rb
74
+ homepage: https://github.com/fguillen/fantasy
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ homepage_uri: https://github.com/fguillen/fantasy
80
+ source_code_uri: https://github.com/fguillen/fantasy
81
+ changelog_uri: https://github.com/fguillen/fantasy/blob/main/CHANGELOG.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.0.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.2.22
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Simple toolbox library and lean API to build great mini games
101
+ test_files: []