Compare commits

...

7 Commits

Author SHA1 Message Date
bionickatana e7825478f1 Finished allowing placement and collision detection. However, the placement is very inconsistent. Thinking about redoing coordinates again... 2026-06-17 22:14:22 -06:00
bionickatana 6f5ad9803f Added selectable gui elements 2026-06-17 13:37:14 -06:00
bionickatana 931773fb12 Reworked the dialouge system. Now the main.rb function can register and delete the dialouges. 2026-06-17 10:50:14 -06:00
bionickatana 564c600150 Added actual images and prepared to add extractors 2026-06-16 21:58:05 -06:00
bionickatana 46d45783f3 Add dialouge boxes/tooltips (#10)
- [x] Update objects so they know when the mouse is on them
- [x] Remove testing code
- [x] Add into the producers
- [x] Add into the consumer
- [x] Standardize fonts

---------

Co-authored-by: leah <leah@hintonclan.org>
Reviewed-on: #10
2026-06-14 22:29:14 -06:00
bionickatana 693f5b83e9 Update main.rb (#8)
This works. I did find out in my testing that it might not set the zoom correct however as soon as you do zoom it works properly. Also odd aspect ratios (eg not my aspect ratio) cause the squares to be not so square

Reviewed-on: #8
Co-authored-by: nathan <nathan@hintonclan.org>
Co-committed-by: nathan <nathan@hintonclan.org>
2026-06-14 15:32:57 -06:00
bionickatana 8e7c0c9078 Fixed coordinate system and finally got zoom working (#1)
Reviewed-on: nathan/number_factory#1
Co-authored-by: bionickatana <nathan@hintonclan.org>
Co-committed-by: bionickatana <nathan@hintonclan.org>
2026-06-14 11:12:32 -06:00
23 changed files with 594 additions and 123 deletions
+33
View File
@@ -1,3 +1,36 @@
# number_factory
A game where you create combinations of numbers!
# UI elements
If you hover over the producers you will see the speed at which it produces.
If you hover over the consumer, you can see your next goal or quest
## tools
### Extractor
Extracts numbers. It is a three by three square and populates numbers from any
side (max 12) every time the producer ticks.
### Conveyer belt
Can move numbers around.
### Adder
Can output to either side but will only output to one space. Prefers the slot
on the left. If it is full though it will use the slots to the right
### Multiplier
### Subtractor
### Divider
### Exponent
### Logarithm
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.
+37 -18
View File
@@ -1,37 +1,56 @@
## Nathan Hinton
## A number consumer that will create numbers when an extractor is placed on it.
require './dialouge_box.rb'
CONSUMER_DEBUG = false
BORDER = 2
SIZE = 3
HOVER_TIME = 500 # ms
# This actually should be stored somewhere else. This is the goals/quests for the game
TASKS = [
"Collect 10 1's"
]
class Consumer
attr_reader :x, :y
attr_reader :draw_x, :draw_y, :size_x, :size_y
# Create consumer at a position with the number and rate specified
# @param x [Integer] X position to start at
# @param y [Integer] Y position to start at
# @param number [Integer] The number to produce
# @param rate [Integer] The speed in numbers per second to produce
def initialize(x, y)
@x = x
@y = y
@image = Gosu.render(30, 30) {Gosu.draw_rect(0, 0, 30, 30, Gosu::Color::GREEN, 0)}
#Gosu::Image.new('assets/images/background_square.png', {:tileable => true})
def initialize(parent, x, y, box_size, screen_width, screen_height)
@parent = parent
@x = x - SIZE / 2
@y = y - SIZE / 2
@box_size = box_size
@screen_width = screen_width
@screen_height = screen_height
@image = Gosu.render((SIZE * @box_size) - (2 * BORDER), (SIZE * @box_size) - (2 * BORDER)) {
Gosu.draw_rect(0, 0, (SIZE * @box_size) - (2 * BORDER), (SIZE * @box_size) - (2 * BORDER), Gosu::Color::GREEN, 0)
}
@task_index = 0
@tooltip = @parent.register_dialouge(@x, @y, @box_size * SIZE, @box_size * SIZE, TASKS[@task_index], 500)
@size_x = @size_y = 3
end
# Creates numbers depending on the rate. This is done with knowing that the
# game is 30 fps so we use that for a counter
# Consumes numbers created
# @param delta_mult [Float] The delta multiplier for any actions
def update(delta_mult)
# @param camera_x [Integer] The camera X position
# @param camera_y [Integer] The camera Y position
# @param camera_zoom [Float] The camera zoom
def update(delta_mult, camera_x, camera_y, camera_zoom, mouse_x, mouse_y)
@draw_x = (((((@screen_width / @box_size.to_f) / 2.0) + (@x - camera_x) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@draw_y = (((((@screen_height / @box_size.to_f) / 2.0) + (@y - camera_y) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@camera_zoom = camera_zoom
@tooltip.move(@draw_x, @draw_y)
end
# Draw the consumer. Uses the camera x and y to shift where it should draw
# @param camera_x [Integer] The camera X position
# @param camera_y [Integer] The camera Y position
# @param camera_zoom [Float] The camera zoom
def draw(camera_x, camera_y, camera_zoom)
if CONSUMER_DEBUG
def draw()
if PRODUCER_DEBUG
puts "Drawing at #{@x - camera_x}, #{@y - camera_y}"
end
@image.draw(camera_zoom * (@x - camera_x), camera_zoom * (@y - camera_y), 0, camera_zoom, camera_zoom)
@image.draw(@draw_x, @draw_y, 0, @camera_zoom, @camera_zoom)
end
end
+73
View File
@@ -0,0 +1,73 @@
# Nathan Hinotn
# Creates a simple dialouge box that can be spawned from any location
TEXT_SIZE = 20
TEXT_MARGIN = 3
class DialougeBox
# @param draw_x [Integer]
# @param draw_y [Integer]
# @param size_x [Integer] The width that this should open at (in pixels at zero zoom)
# @param size_y [Integer] The height that this should open at (in pixels at zero zoom)
def initialize(x, y, size_x, size_y, screen_width, screen_height, text, box_size, hover_time)
@text = text
@longest_line = ((@text.split("\n").map { |ss| ss.length}.max) / 2.25).to_i
@line_count = @text.split("\n").length
@x = x
@y = y
@size_x = size_x
@size_y = size_y
@screen_width = screen_width
@screen_height = screen_height
@box_size = box_size
@hover_time = hover_time
@show = false
@image = Gosu.render(((TEXT_MARGIN * 2) + TEXT_SIZE) * @longest_line, (TEXT_SIZE + TEXT_MARGIN) * @line_count) {
Gosu.draw_rect(
0,
0,
(((TEXT_MARGIN * 2) + TEXT_SIZE) * @longest_line) + @x,
((TEXT_SIZE + TEXT_MARGIN) * @line_count) + @y,
Gosu::Color.new(0xC0, 0xFF, 0xFF, 0xFF),
0,
)
Gosu::Image.from_text(text, TEXT_SIZE, font: FONT_PATH).draw(TEXT_MARGIN, TEXT_MARGIN)
}
@timer = 0
@draw_x = 0
@draw_y = 0
end
# Used to move the dialouge box around
# @param x [Integer] The x position to move to.
# @param y [Integer] The y position to move to.
def move(x, y)
@draw_x = x
@draw_y = y
end
# Update called every delta time
def update(delta_mult, camera_zoom, mouse_x, mouse_y)
@camera_zoom = camera_zoom
if 0 < mouse_x - @draw_x && mouse_x - @draw_x < (@size_x * @camera_zoom) && 0 < mouse_y - @draw_y && mouse_y - @draw_y < (@size_y * @camera_zoom)
@timer += 60.0 * delta_mult
if @timer > @hover_time
@show = true
end
else
@timer = 0
@show = false
end
end
## Draw the dialouge if it is supposed to be shown.
def draw()
if @show
@image.draw(@draw_x, @draw_y, 0, @camera_zoom, @camera_zoom)
end
end
end
+28
View File
@@ -0,0 +1,28 @@
# Nathan Hinton
#
# When placed it will extract numbers at a specified rate and place them on any
# conveyer belts that are adjacent to it.
class Extractor
attr_reader :draw_x, :draw_y, :size_x, :size_y
def initialize(x, y, box_size)
@size_x = @size_y = 3
@x = x - @size_x / 2
@y = y - @size_y / 2
@box_size = box_size
@image = Gosu.render((@size_x * @box_size) - (2 * BORDER), (@size_y * @box_size) - (2 * BORDER)) {
Gosu::Image.new('assets/images/extractor.png').draw(0, 0, 0, @box_size / 32.0, @box_size / 32.0)
Gosu::Image.from_text("#{@number}", 20, font: FONT_PATH).draw(10 / 32.0 * @box_size, (8 / 32.0 * @box_size), 1, 2, 2, 0xFFFA32C0)
}
end
def update(delta_mult, camera_x, camera_y, camera_zoom)
@draw_x = (((((WIDTH / @box_size.to_f) / 2.0) + (@x - camera_x) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@draw_y = (((((HEIGHT / @box_size.to_f) / 2.0) + (@y - camera_y) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@camera_zoom = camera_zoom
end
def draw()
@image.draw(@draw_x, @draw_y, 0, @camera_zoom, @camera_zoom)
end
end
+71
View File
@@ -0,0 +1,71 @@
# Nathan Hinton
# Holds a grid of each otem in the game
class GameGrid
def initialize(game_width, game_height)
@game_grid = []
game_width.times do |xx|
@game_grid.append(Array.new(game_height))
end
end
## Add an item to the grid
# @param x [Integer] The x position (top left corner of item)
# @param y [Integer] The y position (top left corner of item)
# @param item [Object] The item to add. Should be an actual reference to the item
# @retval [Boolean] True if the item was placed, false otherwise (space conflict)
def add_item(x, y, item)
size_x = item.size_x
size_y = item.size_y
puts "#{item.to_s}"
puts "top left: (#{x}, #{y}), size: (#{size_x}, #{size_y})"
# Check that the grid is empty in those spots:
size_x.times do |xx|
size_y.times do |yy|
unless @game_grid[x + xx][y + yy].nil?
puts "Failed to add item: #{item.to_s}:"
puts " - top left: (#{x}, #{y}), size: (#{size_x}, #{size_y}), collision: (#{xx}, #{yy})"
return false
end
end
end
# place item in grid
size_x.times do |xx|
size_y.times do |yy|
@game_grid[x + xx][y + yy] = item
end
end
return true
end
## Add an item to the grid
# @param x [Integer] The x position (top left corner of item)
# @param y [Integer] The y position (top left corner of item)
# @param item [Object] The item to remove. Used to ensure we are removing the right thing.
def remove_item(x, y, item)
size_x = item.size_x
size_y = item.size_y
# Check that the grid is empty in those spots:
size_x.times do |xx|
size_y.times do |yy|
if @game_grid[x + xx][y + yy] != item
raise RuntimeError.new('Item mismatch in internal game grid! Game state corrupted1')
end
end
end
# place item in grid
size_x.times do |xx|
size_y.times do |yy|
@game_grid[x + xx][y + yy] = nil
end
end
end
end
+247 -92
View File
@@ -5,43 +5,68 @@
require 'gosu'
require 'yaml'
require './gamegrid.rb'
require './extractor.rb'
require './producer.rb'
require './consumer.rb'
require './dialouge_box.rb'
require './ui.rb'
FONT_PATH = 'assets/font/Cousine-Regular.ttf'
# Viewport resolution
WIDTH = 640*2
HEIGHT = 480*2
WIDTH = Gosu::screen_width
HEIGHT = Gosu::screen_height
BOX_SIZE = 64
# Default game settings
GAME_SAVE_DIRECTORY = 'game_save_data'
GAME_WIDTH = 10000
GAME_HEIGHT = 10000
GAME_PRODUCER_CHANCE = 0.005
GAME_WIDTH = 201 # Odd numbers look best, in squares
GAME_HEIGHT = 201
GAME_PRODUCER_CHANCE = 0.01
GAME_PRODUCER_VALUE_MULT = 0.1
GAME_PRODUCER_BASE_SPEED = 1
# Camera settings
CAMERA_MOVE_RANGE = 10
CAMERA_MOVE_SPEED = 1
CAMERA_MOVE_MAX_SPEED = (CAMERA_MOVE_RANGE * CAMERA_MOVE_SPEED / 2).to_i
CAMERA_MIN_ZOOM = 0.7
CAMERA_MAX_ZOOM = 5
CAMERA_MOVE_MAX_SPEED = 5
CAMERA_MIN_ZOOM = 16 # Boxes across
CAMERA_MAX_ZOOM = 80 # Boxes across
class NumFactory < Gosu::Window
attr_accessor :current_tool
def initialize
super WIDTH, HEIGHT, :fullscreen => true
self.caption = "Number factory"
@background_color = Gosu::Color::BLUE
@cursor_image = Gosu::Image.new('assets/images/cursor.png')
@cursor = @cursor_image
@cursor_offset = 16
### CAMERA ###
# camera_x [Integer] Used to know the x position of the camera
# camera_y [Integer] Used to know the y position of the camera
# camera_zoom [Float] Used to know the zoom of the camera
@camera_x = 0
@camera_y = 0
@camera_zoom = 1.0
@camera_boxes = 30 # Default zoom
@camera_zoom = 1
@camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
@camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
@ui = UI.new(self, WIDTH, HEIGHT)
@current_tool = nil
@extractor_image = Gosu::Image.new('assets/images/extractor.png')
@belt_image = Gosu::Image.new('assets/images/belt.png')
@adder_image = Gosu::Image.new('assets/images/adder.png')
@delete_image = Gosu::Image.new('assets/images/delete.png')
@dialouges = []
@extractors = []
# Setup the game grid
@game_grid = GameGrid.new(GAME_WIDTH, GAME_HEIGHT)
# Load from file or initialize a new game
begin
game_save_data = YAML.load_file("#{GAME_SAVE_DIRECTORY}/positions.yaml")
puts game_save_data
@@ -65,39 +90,51 @@ class NumFactory < Gosu::Window
# Loop through and place producers
center_x = (@game_width / 60).to_i
center_y = (@game_height / 60).to_i
# Move the camera to the center of the map:
@camera_x = center_x * 30 - WIDTH / 2
@camera_y = center_y * 30 - HEIGHT / 2
center_x = ((@game_width - 1) / 2).to_i
center_y = ((@game_height - 1) / 2).to_i
@consumers.append(
Consumer.new(center_x * 30, center_y * 30),
)
consumer = Consumer.new(self, center_x, center_y, BOX_SIZE, WIDTH, HEIGHT)
if @game_grid.add_item(center_x, center_y, consumer)
@consumers.append(consumer)
end
# The value of the producer is proportional to the distance from the center
((@game_width / 30).to_i).times do |xx|
(@game_height / 30).to_i.times do |yy|
((@game_width).to_i).times do |xx|
(@game_height).to_i.times do |yy|
if rand < GAME_PRODUCER_CHANCE
# Place a producer:
@producers.append(
producer =
Producer.new(
xx * 30,
yy * 30,
self,
xx,
yy,
(Math.sqrt((xx - center_x)**2 + (yy - center_y) ** 2) * GAME_PRODUCER_VALUE_MULT).to_i,
GAME_PRODUCER_BASE_SPEED)
)
GAME_PRODUCER_BASE_SPEED,
BOX_SIZE,
WIDTH,
HEIGHT,
)
# Add an item to the game with checking that the internal game state is good
if @game_grid.add_item(xx, yy, producer)
@producers.append(producer)
end
end
end
end
end # End file loading
@background = Gosu.render(@game_width, @game_height) {
# Move the camera to the center of the map:
@camera_x = @game_width / 2
@camera_y = @game_height / 2
# Render the background
@background = Gosu.render(@game_width * BOX_SIZE, @game_height * BOX_SIZE) {
im = Gosu::Image.new('assets/images/background_square.png', {:tileable => true})
(@game_width / 30).to_i.times do |ww|
(@game_height / 30).to_i.times do |hh|
im.draw(ww * 30, hh * 30)
zoom = BOX_SIZE / 30.0
(@game_width).to_i.times do |ww|
puts ww
(@game_height).to_i.times do |hh|
im.draw(ww * BOX_SIZE, hh * BOX_SIZE, 0, zoom, zoom)
end
end
}
@@ -107,78 +144,165 @@ class NumFactory < Gosu::Window
end
# Disable cursor in the window
def needs_cursor?
false
end
def button_down(id)
### Process mouse ###
mouse_used = @ui.update(self.mouse_x, self.mouse_y)
# They clicked in the game screen, attempt to place an item
if !mouse_used && id == Gosu::MS_LEFT
case @current_tool
when 'delete'
@cursor = @delete_image
@cursor_offset = @delete_image.height
#break
when 'belt'
@cursor = @belt_image
@cursor_offset = @belt_image.height
#break
when 'adder'
@cursor = @adder_image
@cursor_offset = @adder_image.height
#break
when 'extractor'
#xx = @camera_x + ((((self.mouse_x) - (WIDTH/2)) / BOX_SIZE - 1) * @camera_zoom).to_i
#yy = @camera_y + ((((self.mouse_y) - (HEIGHT/2)) / BOX_SIZE - 1) * @camera_zoom).to_i
xx = @camera_x + ((self.mouse_x - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom)).to_i
yy = @camera_y + ((self.mouse_y - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom))
extractor = Extractor.new(xx, yy, BOX_SIZE)
if @game_grid.add_item(xx, yy, extractor)
@extractors.append(extractor)
end
#break
end
end
### Process keyboard ###
if id == Gosu::KB_ESCAPE
close
elsif id == Gosu::MS_WHEEL_UP
@camera_zoom *= 1.1
if @camera_zoom > CAMERA_MAX_ZOOM
@camera_zoom = CAMERA_MAX_ZOOM
else
@camera_x *= 1.0125
@camera_y *= 1.0125
elsif [Gosu::MS_WHEEL_UP, Gosu::KB_Z].include? id
@camera_boxes -= 2
if @camera_boxes < CAMERA_MIN_ZOOM
@camera_boxes = CAMERA_MIN_ZOOM
end
elsif id == Gosu::MS_WHEEL_DOWN
@camera_zoom *= 0.9
if @camera_zoom < CAMERA_MIN_ZOOM
@camera_zoom = CAMERA_MIN_ZOOM
else
@camera_x *= 0.985
@camera_y *= 0.985
@camera_zoom = WIDTH / (@camera_boxes * BOX_SIZE).to_f
@camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
@camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
elsif [Gosu::MS_WHEEL_DOWN, Gosu::KB_X].include? id
@camera_boxes += 2
if @camera_boxes > CAMERA_MAX_ZOOM
@camera_boxes = CAMERA_MAX_ZOOM
end
@camera_zoom = WIDTH.to_f / (@camera_boxes * BOX_SIZE).to_f
@camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
@camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
elsif id == Gosu::KB_DOWN
@camera_y += 1
elsif id == Gosu::KB_UP
@camera_y -= 1
elsif id == Gosu::KB_LEFT
@camera_x -= 1
elsif id == Gosu::KB_RIGHT
@camera_x += 1
elsif id == Gosu::KB_SPACE
@camera_zoom = 1.0
@camera_boxes = 30
@camera_zoom = WIDTH.to_f / (@camera_boxes * BOX_SIZE).to_f
@camera_x_zoom_offset = WIDTH.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
@camera_y_zoom_offset = HEIGHT.to_f / (@camera_boxes * BOX_SIZE * 2).to_f
elsif id == Gosu::KB_G
@cursor = @cursor_image
@cursor_offset = 16
@current_tool = nil
else
super
end
end
## Register a dialouge into the game
# @param x [Integer] The x position that the dialouge should be drawn at.
# @param y [Integer] The y position that the dialouge should be drawn at.
# @param size_x [Integer] The pixel width
# @param size_y [Integer] The pixel width
# @param text [String] The text for the dialouge box
def register_dialouge(x, y, size_x, size_y, text, popup_time)
dialouge = DialougeBox.new(x, y, size_x, size_y, WIDTH, HEIGHT, text, BOX_SIZE, popup_time)
@dialouges.append(dialouge)
return dialouge
end
## Remove a dialouge from the game
# @param dialouge [DialougeBox] The dialouge to remove
def deregister_dialouge(dialouge)
@dialouges.delete(dialouge)
end
## Called every frame
def update
@delta_time = Time.now() - @tick_time
delta_mult = @delta_time * 30
#puts "Time: #{@delta_time}, Mult: #{delta_mult}" # Timing debug
@tick_time = Time.now()
### CAMERA ###
# Move the camera if needed
if self.mouse_x < CAMERA_MOVE_RANGE
@camera_x -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_x ) / 2)).to_i, CAMERA_MOVE_MAX_SPEED].min * delta_mult * @camera_zoom)
@camera_x -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_x ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult)
elsif self.mouse_x > WIDTH - CAMERA_MOVE_RANGE
@camera_x += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (WIDTH - self.mouse_x) ) / 2)).to_i, CAMERA_MOVE_MAX_SPEED].min * delta_mult * @camera_zoom)
@camera_x += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (WIDTH - self.mouse_x + 1)) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult)
end
if self.mouse_y < CAMERA_MOVE_RANGE
@camera_y -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_y ) / 2)).to_i, CAMERA_MOVE_MAX_SPEED].min * delta_mult)
@camera_y -= ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - self.mouse_y ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult)
elsif self.mouse_y > HEIGHT - CAMERA_MOVE_RANGE
@camera_y += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (HEIGHT - (self.mouse_y + 1)) ) / 2)).to_i, CAMERA_MOVE_MAX_SPEED].min * delta_mult)
@camera_y += ([(CAMERA_MOVE_SPEED * ((CAMERA_MOVE_RANGE - (HEIGHT - (self.mouse_y + 1)) ) / 5)), CAMERA_MOVE_MAX_SPEED].min * delta_mult)
end
# Prevent camera from leaving the screen
@camera_x = [@camera_x, -10].max
@camera_x = [@camera_x, @game_width - (WIDTH / @camera_zoom) + 10].min
@camera_y = [@camera_y, -10].max
@camera_y = [@camera_y, @game_height - (HEIGHT / @camera_zoom) + 10].min
# Setup variables used to know what portions of the screen need to be rendered
@camera_max_x = [@game_width, @camera_x + (WIDTH / @camera_zoom)].min
@camera_min_x = [0, @camera_x].max
@camera_max_y = [@game_height, @camera_y + (WIDTH / @camera_zoom)].min
@camera_min_y = [0, @camera_y].max
@camera_x = [@camera_x.to_i, 0].max
@camera_x = [@camera_x.to_i, @game_width].min
@camera_y = [@camera_y.to_i, 0].max
@camera_y = [@camera_y.to_i, @game_width].min
### ITEMS ###
@producers.each do |producer|
producer.update(delta_mult)
producer.update(delta_mult, @camera_x, @camera_y, @camera_zoom)
end
@belts.each do |belt|
belt.update(delta_mult)
@extractors.each do |extractor|
extractor.update(delta_mult, @camera_x, @camera_y, @camera_zoom)
end
@consumers.each do |consumer|
consumer.update(delta_mult)
consumer.update(delta_mult, @camera_x, @camera_y, @camera_zoom, self.mouse_x, self.mouse_y)
end
# Finally process the dialouges
@dialouges.each do |dialouge|
dialouge.update(delta_mult, @camera_zoom, self.mouse_x, self.mouse_y)
end
case @current_tool
when 'delete'
@cursor = @delete_image
@cursor_offset = @delete_image.height
#break
when 'belt'
@cursor = @belt_image
@cursor_offset = @belt_image.height
#break
when 'adder'
@cursor = @adder_image
@cursor_offset = @adder_image.height
#break
when 'extractor'
@cursor = @extractor_image
@cursor_offset = @extractor_image.height
#break
end
end
@@ -186,37 +310,68 @@ class NumFactory < Gosu::Window
def draw
# Draw mouse coords:
onscreen_items = 0
Gosu.draw_rect(
[(@camera_min_x - @camera_x) * @camera_zoom, self.width].min,
[(@camera_min_y - @camera_y) * @camera_zoom, self.width].min,
[self.width, (@camera_max_x - @camera_x) * @camera_zoom].min,
[self.height, (@camera_max_y - @camera_y) * @camera_zoom].min,
@background_color, 0)
@background.draw(0, 0, 0, @camera_zoom, @camera_zoom)
# TODO: The background does *not* actually need to move. It can just stay
# in one spot and scale... This would make the program load so much faster
# as well as allow for larger maps.
@background.draw(
(((((WIDTH / BOX_SIZE.to_f) / 2.0) - (@camera_x * @camera_zoom)) * (BOX_SIZE))),
((((HEIGHT / BOX_SIZE.to_f) / 2.0) - (@camera_y * @camera_zoom)) * (BOX_SIZE)),
0,
@camera_zoom,
@camera_zoom
)
@producers.each do |producer|
if producer.x <= @camera_max_x && producer.x >= @camera_min_x
if producer.y <= @camera_max_y && producer.y >= @camera_min_y
producer.draw(@camera_x, @camera_y, @camera_zoom)
if producer.draw_x <= WIDTH && producer.draw_x >= 0
if producer.draw_y <= HEIGHT && producer.draw_y >= 0
producer.draw()
onscreen_items += 1
end
end
end
@extractors.each do |extractor|
if extractor.draw_x <= WIDTH && extractor.draw_x >= 0
if extractor.draw_y <= HEIGHT && extractor.draw_y >= 0
extractor.draw()
onscreen_items += 1
end
end
end
@consumers.each do |consumer|
if consumer.x <= (@camera_x + WIDTH) && consumer.x >= (@camera_x)
if consumer.y <= (@camera_y + WIDTH) && consumer.y >= (@camera_y)
consumer.draw(@camera_x, @camera_y, @camera_zoom)
if consumer.draw_x <= WIDTH && consumer.draw_x >= 0
if consumer.draw_y <= HEIGHT && consumer.draw_y >= 0
consumer.draw()
onscreen_items += 1
end
end
end
Gosu::Image.from_text("#{mouse_x}, #{mouse_y}", 20).draw(0, 100, 1)
Gosu::Image.from_text("Tick time: #{((Time.now() - @tick_time).to_f * 1000).to_i}", 20).draw(0, 25, 1)
Gosu::Image.from_text("FPS: #{Gosu.fps}", 20).draw(0, 50, 1)
Gosu::Image.from_text("items on screen: #{onscreen_items}", 20).draw(0, 75, 1)
Gosu::Image.from_text("#{width}, #{height}", 20).draw(0, 0, 1)
Gosu::Image.from_text("Camera location: #{@camera_x}, #{@camera_y}", 20).draw(0, 200, 1)
Gosu::Image.from_text("Camera zoom: #{@camera_zoom}", 20).draw(0, 225, 1)
Gosu::Image.from_text("Camera viewport: (#{@camera_min_x}, #{@camera_min_y}) to (#{@camera_max_x}, #{@camera_max_y})", 20).draw(0, 250, 1)
@dialouges.each do |dialouge|
dialouge.draw()
end
@ui.draw()
Gosu::Image.from_text("#{width}, #{height}", 20, font: FONT_PATH).draw(1600, 0, 1)
Gosu::Image.from_text("#{mouse_x}, #{mouse_y}", 20, font: FONT_PATH).draw(1600, 100, 1)
Gosu::Image.from_text("#{@camera_x + ((self.mouse_x - @cursor_offset + (BOX_SIZE * @camera_zoom)) / (BOX_SIZE * @camera_zoom)).to_i}, #{@camera_y + ((self.mouse_y - (HEIGHT/2)) / BOX_SIZE * @camera_zoom).to_i}", 20, font: FONT_PATH).draw(1600, 125, 1)
Gosu::Image.from_text("Tick time: #{((Time.now() - @tick_time).to_f * 1000).to_i}", 20, font: FONT_PATH).draw(1600, 25, 1)
Gosu::Image.from_text("FPS: #{Gosu.fps}", 20, font: FONT_PATH).draw(1600, 50, 1)
Gosu::Image.from_text("items on screen: #{onscreen_items}", 20, font: FONT_PATH).draw(1600, 75, 1)
Gosu::Image.from_text("Camera boxes: #{@camera_boxes}", 20, font: FONT_PATH).draw(1600, 175, 1)
Gosu::Image.from_text("Camera location: #{@camera_x}, #{@camera_y}", 20, font: FONT_PATH).draw(1600, 200, 1)
Gosu::Image.from_text("Camera zoom: #{@camera_zoom}, #{@camera_x_zoom_offset}", 20, font: FONT_PATH).draw(1600, 225, 1)
Gosu::Image.from_text("Camera viewport: (#{@camera_min_x}, #{@camera_min_y}) to (#{@camera_max_x}, #{@camera_max_y})", 20, font: FONT_PATH).draw(1600, 250, 1)
if @cursor == @cursor_image
@cursor.draw(self.mouse_x - @cursor_offset, self.mouse_y - @cursor_offset, 10)
else
@cursor.draw(self.mouse_x - @cursor_offset, self.mouse_y - @cursor_offset, 10, BOX_SIZE / 32.0 * @camera_zoom, BOX_SIZE / 32.0 * @camera_zoom, 0x88_888888)
end
Gosu.draw_rect(self.mouse_x - 2, self.mouse_y - 2, 4, 4, Gosu::Color::RED)
end
end
+26 -13
View File
@@ -2,43 +2,56 @@
## A number producer that will create numbers when an extractor is placed on it.
PRODUCER_DEBUG = false
BORDER = 2
class Producer
attr_reader :x, :y
attr_reader :draw_x, :draw_y, :size_x, :size_y
# Create producer at a position with the number and rate specified
# @param x [Integer] X position to start at
# @param y [Integer] Y position to start at
# @param number [Integer] The number to produce
# @param rate [Integer] The speed in numbers per second to produce
def initialize(x, y, number, rate)
def initialize(parent, x, y, number, rate, box_size, screen_width, screen_height)
@x = x
@y = y
@parent = parent
@number = [number, 1].max
@rate = (30/rate).to_i
@image = Gosu.render(30, 30) {
@rate = rate
@box_size = box_size
@screen_width = screen_width
@screen_height = screen_height
@image = Gosu.render(@box_size - (2 * BORDER), @box_size - (2 * BORDER)) {
# Render the item
Gosu.draw_rect(0, 0, 30, 30, Gosu::Color::RED, 0)
Gosu::Image.from_text("#{@number}", 20).draw(5, 5, 1)
Gosu::Image.new('assets/images/producer.png').draw(0, 0, 0, @box_size / 32.0, @box_size / 32.0)
Gosu::Image.from_text("#{@number}", 20, font: FONT_PATH).draw(10 / 32.0 * @box_size, (8 / 32.0 * @box_size), 1, 2, 2, 0xFFFA32C0)
}
puts "X: #{@x}, Y: #{@y}"
@tooltip = @parent.register_dialouge(@x, @y, @box_size, @box_size, "Produces #{@rate} number per second", 500)
@size_x = @size_y = 1
end
# Creates numbers depending on the rate. This is done with knowing that the
# game is 30 fps so we use that for a counter
# @param delta_mult [Float] The delta multiplier for any actions
def update(delta_mult)
end
# Draw the producer. Uses the camera x and y to shift where it should draw
# @param camera_x [Integer] The camera X position
# @param camera_y [Integer] The camera Y position
# @param camera_zoom [Float] The camera zoom
def draw(camera_x, camera_y, camera_zoom)
def update(delta_mult, camera_x, camera_y, camera_zoom)
@draw_x = (((((@screen_width / @box_size.to_f) / 2.0) + (@x - camera_x) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@draw_y = (((((@screen_height / @box_size.to_f) / 2.0) + (@y - camera_y) * camera_zoom) * @box_size) + BORDER * camera_zoom)
@camera_zoom = camera_zoom
@tooltip.move(@draw_x, @draw_y)
end
# Draw the producer. Uses the camera x and y to shift where it should draw
def draw()
if PRODUCER_DEBUG
puts "Drawing at #{@x - camera_x}, #{@y - camera_y}"
end
@image.draw(camera_zoom * (@x - camera_x), camera_zoom * (@y - camera_y), 0, camera_zoom, camera_zoom)
@image.draw(@draw_x, @draw_y, 0, @camera_zoom, @camera_zoom)
end
end
+79
View File
@@ -0,0 +1,79 @@
# Nathan Hinton
# This is the UI for the game and will contain all of the sub elements required.
# There will be three main areas that we will be in charge of. First, the
# bottom bar has the selector of item types. There will be a menu in the top
# right as well as any store things that we need/
class UI
# Setup for the UI
# @param width [Integer] Width of screen in pixels
# @param height [Integer] Height of the screen in pixels
def initialize(parent, width, height)
@parent = parent
@width = width
@height = height
@tool_selector = []
@toolbar_width = width / 2
@toolbar_height = height / 9
@tool_info = [
['extractor', 96],
['belt', 32],
['adder', 64],
['delete', 32],
]
@tool_selector_image = Gosu.render(@toolbar_width, @toolbar_height) {
Gosu.draw_rect(
0,
0,
@toolbar_width,
@toolbar_height,
Gosu::Color.new(0xC0, 0xFF, 0xFF, 0xFF),
0)
# Loop through the tools:
@tool_info.each_with_index do |path, idx|
Gosu::Image.new("assets/images/#{path[0]}.png").draw(
idx * (@toolbar_height + 2),
2,
0,
(@toolbar_height / path[1].to_f),
(@toolbar_height / path[1].to_f),
)
end
}
@x = (@width / 2) - (@toolbar_width / 2)
@y = @height - @toolbar_height
@mouse_down_last_update = false
end
def update(mouse_x, mouse_y)
managed = false
# Only check if we are clicking
if Gosu.button_down?(Gosu::MS_LEFT)
if !@mouse_down_last_update
puts 'mouse clicked'
@mouse_down_last_update = true
# Check if we are in the right y area:
if mouse_y > @y
# Check if we are within the right zone
mouse_x_inset = mouse_x - @x
if 0 < mouse_x_inset && mouse_x_inset < @toolbar_width
managed = true
item_index = mouse_x_inset / (@toolbar_height + 2)
if item_index < @tool_info.length
@parent.current_tool = @tool_info[item_index][0]
puts "Selected item!"
end
end
end
end
else
@mouse_down_last_update = false
end
return managed
end
def draw()
@tool_selector_image.draw(@x, @y)
end
end