Compare commits

...

9 Commits

35 changed files with 531 additions and 43 deletions

View File

@@ -1,25 +1,37 @@
PATH
remote: .
specs:
cyberarm_engine (0.24.5)
cyberarm_engine (0.30.0)
gosu (~> 1.1)
GEM
remote: https://rubygems.org/
specs:
gosu (1.4.6)
minitest (5.25.5)
rake (13.2.1)
ffi (1.17.4-x86_64-linux-gnu)
ffi (1.17.4-x86_64-linux-musl)
gosu (1.5.0-x86_64-linux)
ffi (~> 1.12)
minitest (5.27.0)
rake (13.4.2)
PLATFORMS
x64-mingw-ucrt
x86_64-linux
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
bundler (~> 2.2)
bundler (~> 4)
cyberarm_engine!
minitest (~> 5.0)
rake (~> 13.0)
CHECKSUMS
bundler (4.1.0.beta1) sha256=8a2dac8ca276ff4eff5efc2c6457b0044080fc1e431534ad7c5cc6187232b800
cyberarm_engine (0.30.0)
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
gosu (1.5.0-x86_64-linux)
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
BUNDLED WITH
2.6.8
4.1.0.beta1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -28,9 +28,8 @@ Gem::Specification.new do |spec|
spec.require_paths = %w[lib assets]
spec.add_dependency "gosu", "~> 1.1"
# spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows
spec.add_development_dependency "bundler", "~> 2.2"
spec.add_development_dependency "bundler", "~> 4"
spec.add_development_dependency "minitest", "~> 5.0"
spec.add_development_dependency "rake", "~> 13.0"
end

View File

@@ -42,6 +42,22 @@ require_relative "cyberarm_engine/console/command"
require_relative "cyberarm_engine/console/subcommand"
require_relative "cyberarm_engine/console/commands/help_command"
require_relative "cyberarm_engine/pathfinding/graph"
require_relative "cyberarm_engine/pathfinding/graphs/grid"
require_relative "cyberarm_engine/pathfinding/graph_search"
require_relative "cyberarm_engine/pathfinding/graph_searches/breadth_first_search"
require_relative "cyberarm_engine/pathfinding/graph_searches/dijkstra_search"
require_relative "cyberarm_engine/pathfinding/graph_searches/astar_search"
require_relative "cyberarm_engine/ecs/entity"
require_relative "cyberarm_engine/ecs/component"
require_relative "cyberarm_engine/ecs/system"
require_relative "cyberarm_engine/ecs/entity_store"
require_relative "cyberarm_engine/ecs/components/position"
require_relative "cyberarm_engine/ecs/components/velocity"
require_relative "cyberarm_engine/ecs/components/render_2d"
require_relative "cyberarm_engine/ecs/components/team"
require_relative "cyberarm_engine/ui/dsl"
require_relative "cyberarm_engine/ui/theme"

View File

@@ -0,0 +1,119 @@
module CyberarmEngine
module ECS
# Components MUST not have "logic," only data.
class Component
InternalEncoder = Data.define(:pack, :min, :max, :encoder, :decoder)
# Note: all encoders use big endian
INTERNAL_ENCODING_DATA = {
float: InternalEncoder.new("g", -Float::INFINITY, Float::INFINITY, nil, nil),
double: InternalEncoder.new("G", -Float::INFINITY, Float::INFINITY, nil, nil),
int8: InternalEncoder.new("c", -128, 127, nil, nil),
uint8: InternalEncoder.new("C", 0, 255, nil, nil),
int16: InternalEncoder.new("s>", -32_768, 32_767, nil, nil),
uint16: InternalEncoder.new("n", 0, 65_535, nil, nil),
int32: InternalEncoder.new("l>", -2_147_483_648, 2_147_483_647, nil, nil),
uint32: InternalEncoder.new("N", 0, 4_294_967_295, nil, nil),
int64: InternalEncoder.new("q>", -9_223_372_036_854_775_808, 9_223_372_036_854_775_807, nil, nil),
uint64: InternalEncoder.new("Q>", 0, 18_446_744_073_709_551_615, nil, nil),
string: InternalEncoder.new("Z", nil, nil, nil, nil),
vector_float: InternalEncoder.new("g4", nil, nil, ->(v) { [v.x, v.y, v.z, v.w] }, ->(v, a) { v.x = a[0]; v.y = a[1]; v.z = a[2]; v.w = a[4] }),
vector_double: InternalEncoder.new("G4", nil, nil, ->(v) { [v.x, v.y, v.z, v.w] }, ->(v, a) { v.x = a[0]; v.y = a[1]; v.z = a[2]; v.w = a[4] }),
vector_int8: InternalEncoder.new("c4", nil, nil, ->(v) { [v.x, v.y, v.z, v.w] }, ->(v, a) { v.x = a[0]; v.y = a[1]; v.z = a[2]; v.w = a[4] }),
vector_int16: InternalEncoder.new("s>4", nil, nil, ->(v) { [v.x, v.y, v.z, v.w] }, ->(v, a) { v.x = a[0]; v.y = a[1]; v.z = a[2]; v.w = a[4] }),
vector_int32: InternalEncoder.new("l>4", nil, nil, ->(v) { [v.x, v.y, v.z, v.w] }, ->(v, a) { v.x = a[0]; v.y = a[1]; v.z = a[2]; v.w = a[4] }),
}.freeze
@identifier = nil
@values = []
def self.register_value(type, name, value, encoder = INTERNAL_ENCODING_DATA[type])
pp [type, name, value, encoder]
end
def self.register_custom_value(type, name, value, packed, encoder, decoder)
pp [type, name, value, packed, encoder, decoder]
end
def self.identifier(value = nil)
unless value
value = [File.basename(__FILE__).bytes.each_slice(4).map(&:sum).sum^0xffffffff].pack("N").unpack1("N")
end
@identifier = value
end
def self.float(name, value = 0.0)
register_value(:float, name, value)
end
def self.double(name, value = 0.0)
register_value(:double, name, value)
end
def self.int8(name, value = 0)
register_value(:int8, name, value)
end
def self.uint8(name, value = 0)
register_value(:uint8, name, value)
end
def self.int16(name, value = 0)
register_value(:int16, name, value)
end
def self.uint16(name, value = 0)
register_value(:uint16, name, value)
end
def self.int32(name, value = 0)
register_value(:int32, name, value)
end
def self.uint32(name, value = 0)
register_value(:uint32, name, value)
end
def self.int64(name, value = 0)
register_value(:int64, name, value)
end
def self.uint64(name, value = 0)
register_value(:uint64, name, value)
end
def self.string(name, value = "")
register_value(:string, name, value)
end
def self.vector_float(name, value = CyberarmEngine.new(0.0, 0.0, 0.0, 0.0))
register_value(:vector_float, name, value)
end
def self.vector_double(name, value = CyberarmEngine.new(0.0, 0.0, 0.0, 0.0))
register_value(:vector_double, name, value)
end
def self.vector_int8(name, value = CyberarmEngine.new(0, 0, 0, 0))
register_value(:vector_int8, name, value)
end
def self.vector_int16(name, value = CyberarmEngine.new(0, 0, 0, 0))
register_value(:vector_int16, name, value)
end
def self.vector_int32(name, value = CyberarmEngine.new(0, 0, 0, 0))
register_value(:vector_int32, name, value)
end
def self.custom(name, value, pack, encoder, decoder)
register_value(:custom, name, value, pack, encoder, decoder)
end
def self.custom_unserializable(name, value)
register_value(:custom, name, value)
end
end
end
end

View File

@@ -0,0 +1,19 @@
module CyberarmEngine
module ECS
class Component
class Position < Component
TYPE = 0x00
attr_reader :position
def initialize(position:)
unless position.is_a?(CyberarmEngine::Vector)
raise "Position must be a CyberarmEngine::Vector, got #{position.class}"
end
@position = position
end
end
end
end
end

View File

@@ -0,0 +1,24 @@
module CyberarmEngine
module ECS
class Component
class Render2D < Component
TYPE = 0xf0
attr_reader :image, :scale, :center
def initialize(image:, scale: CyberarmEngine::Vector.new(1.0, 1.0),
center: CyberarmEngine::Vector.new(0.5, 0.5))
raise "Image must be a Gosu::Image, got #{image.class}" unless image.is_a?(Gosu::Image)
raise "Scale must be a CyberarmEngine::Vector, got #{scale.class}" unless scale.is_a?(CyberarmEngine::Vector)
unless center.is_a?(CyberarmEngine::Vector)
raise "Center must be a CyberarmEngine::Vector, got #{center.class}"
end
@image = image
@scale = scale
@center = center
end
end
end
end
end

View File

@@ -0,0 +1,17 @@
module CyberarmEngine
module ECS
class Component
class Team < Component
TYPE = 0x03
attr_accessor :team
def initialize(team:)
raise "Team must be an Integer, got #{team.class}" unless team.is_a?(Integer)
@team = team
end
end
end
end
end

View File

@@ -0,0 +1,19 @@
module CyberarmEngine
module ECS
class Component
class Velocity < Component
TYPE = 0x01
attr_reader :velocity
def initialize(velocity:)
unless velocity.is_a?(CyberarmEngine::Vector)
raise "Velocity must be a CyberarmEngine::Vector, got #{velocity.class}"
end
@velocity = velocity
end
end
end
end
end

View File

@@ -0,0 +1,35 @@
module CyberarmEngine
module ECS
class Entity
attr_reader :entity_id, :entity_components
def initialize(id:)
@entity_id = id
# there are more cache friendly ways to implement component storage/lookup but right now this is "good enough."
@entity_components = []
end
def add_component(component)
raise "Component already exists on entity!" if @entity_components.find { |c| c.is_a?(component) }
@entity_components << component
end
def component(component)
@entity_components.find { |c| c.is_a?(component) }
end
def components(components)
components.each do |c|
return false unless @entity_components.include? { |comp| c.is_a?(comp) }
end
true
end
def remove_component(component)
@entity_components.delete_if { |c| c.is_a?(component) }
end
end
end
end

View File

@@ -0,0 +1,56 @@
module CyberarmEngine
module ECS
class EntityStore
MAX_ENTITY_ID = 4_294_967_295
def initialize
@current_entity_id = -1
@entities = []
@systems = [
System::Movement,
System::Render2D
]
end
def create_entity(components)
ent = Entity.new(id: next_entity_id)
components.each do |component|
ent.add_component(component)
end
@entities << ent
ent
end
def find_entity(id)
@entities.find { |e| e.id == id }
end
def entities_with(components)
@entities.select { |e| e.components(components) }
end
def remove_entity(entity)
@entities.delete_if { |e| e == entity }
end
def remove_entities(entities)
@entities.delete_if { |e| entities.include?(e) }
end
def remove_all_entities
@entities.clear
end
def next_entity_id
# NOTE: we'll be sending entity IDs as uint32's (4 bytes with MAX value of MAX_ENTITY_ID [4.29 billion])
raise "Next entity ID exceeds maximium value!" if @current_entity_id + 1 > MAX_ENTITY_ID
@current_entity_id += 1
end
end
end
end

View File

@@ -0,0 +1,14 @@
module CyberarmEngine
module ECS
class System
# Array of required components for this system
def truple
[]
end
def update(entity); end
def fixed_update(entity); end
end
end
end

View File

@@ -0,0 +1,18 @@
module CyberarmEngine
module ECS
class System
class Movement < System
def truple
[
Component::Position,
Component::Velocity
]
end
def update(entity_store); end
def fixed_update(entity_store); end
end
end
end
end

View File

@@ -0,0 +1,16 @@
module CyberarmEngine
module ECS
class System
class Render < System
def truple
[
Component::Position,
Component::Render
]
end
def draw(entity_store); end
end
end
end
end

View File

@@ -53,6 +53,18 @@ module CyberarmEngine
def gamepad_disconnected(index)
end
def touch_began(touch)
end
def touch_moved(touch)
end
def touch_ended(touch)
end
def touch_cancelled(touch)
end
def gain_focus
end

View File

@@ -1,14 +1,63 @@
module CyberarmEngine
module Pathfinding
class Graph
Location = Data.define(:x, :y, :z, :cost)
class Node
KEY_FORMAT = "%s:%s:%s".freeze
def initialize(edges: {})
@edges = edges
attr_reader :x, :y, :z, :cost, :key, :edge_nodes
def initialize(x:, y:, z: 0, cost: 0)
@x = x
@y = y
@z = z
@cost = cost
@key = format(KEY_FORMAT, @x, @y, @z).freeze
@edge_nodes = []
end
def add_edge_node(node)
index = @edge_nodes.find_index { |n| n.key == node.key }
return if index
@edge_nodes.push(node)
end
def remove_edge_node(node)
index = @edge_nodes.find_index { |n| n.key == node.key }
return unless index
@edge_nodes.delete(index)
end
def inspect
format "#<%<class>s:%<id>#018x @key=%<key>s @cost=%<cost>s @edge_nodes=%<edge_nodes>s>",
class: self.class,
id: object_id << 1,
key: key.inspect,
cost: cost.inspect,
edge_nodes: edge_nodes.map { |n| n&.key }
end
end
def neighbors(location)
@edges[location]
def initialize(nodes: {})
@nodes = nodes
end
def node(key)
@nodes[key]
end
def node_at(x, y, z = 0)
key = format(Node::KEY_FORMAT, x, y, z)
@nodes[key]
end
def neighbors(node)
node.edge_nodes
end
end
end

View File

@@ -1,22 +1,22 @@
module CyberarmEngine
module Pathfinding
class BreadthFirstSearch < GraphSearch
def search(graph:, start:, goal: nil)
def self.search(graph:, start:, goal: nil)
frontier = []
came_from = {}
frontier.put(start)
came_from[start] = start
frontier.push(start)
came_from[start.key] = start
while(current = frontier.shift)
# early exit if we reach our goal
break if current == goal
graph.neighbors(current).each do |neighbor|
next if came_from[neighbor]
next if came_from[neighbor.key]
frontier.put(neighbor)
came_from[neighbor] = current
frontier.push(neighbor)
came_from[neighbor.key] = current
end
end

View File

@@ -1,32 +1,51 @@
module CyberarmEngine
module Pathfinding
class Grid < Graph
Cell = Data.define(:location, :blocker, :cost)
def add_node(x:, y:, z: 0, cost: 0, replace: false)
key = format(Node::KEY_FORMAT, x, y, z)
node = @nodes[key]
# nodes: a 2d array of nodes to convert to list of edges
def self.edges_from_world_grid(nodes:)
edges = {}
raise "Node at: #{key} already exists! (#{node})" if node && !replace
nodes.each_index do |y, y_index|
y.each_with_index do |x_index|
node = nodes[y_index][x_index]
next unless node
node = Node.new(x: x, y: y, z: z, cost: cost)
@nodes[key] = node
left = nodes[y_index]&.[x_index - 1]
right = nodes[y_index]&.[x_index + 1]
top = nodes[y_index - 1]&.[x_index]
bottom = nodes[y_index + 1]&.[x_index]
update_node_edge_nodes(node)
node
end
location = Location.new(x_index, y_index, 0, 0)
edges[location] = []
edges[location].push(Location.new(x_index - 1, y_index, 0, 0)) if left
edges[location].push(Location.new(x_index + 1, y_index, 0, 0)) if right
edges[location].push(Location.new(x_index, y_index - 1, 0, 0)) if top
edges[location].push(Location.new(x_index, y_index + 1, 0, 0)) if bottom
end
def remove_node(node)
update_node_edge_nodes(node, remove_node: true)
@nodes.delete(node.key)
end
def update_node_edge_nodes(node, remove_node: false)
x = node.x
y = node.y
z = node.z
# add node to neighbors
# 2D
left = @nodes[format(Node::KEY_FORMAT, x - 1, y, z)]
right = @nodes[format(Node::KEY_FORMAT, x + 1, y, z)]
top = @nodes[format(Node::KEY_FORMAT, x, y - 1, z)]
bottom = @nodes[format(Node::KEY_FORMAT, x, y + 1, z)]
# 3D
front = @nodes[format(Node::KEY_FORMAT, x, y, z - 1)]
back = @nodes[format(Node::KEY_FORMAT, x, y, z + 1)]
possible_edge_nodes = [left, right, top, bottom, front, back]
if remove_node
possible_edge_nodes.each { |n| n&.remove_edge_node(node) }
else
possible_edge_nodes.each { |n| n&.add_edge_node(node) }
end
end
new(edges: edges)
def update_edge_nodes
@nodes.each { |k, n| update_node_edge_nodes(n) }
end
end
end

View File

@@ -5,7 +5,7 @@ module CyberarmEngine
include Common
attr_accessor :x, :y, :z, :tip, :element_visible
attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
attr_reader :parent, :options, :style, :event_handler, :background_image_canvas, :background_canvas, :border_canvas
def initialize(options = {}, block = nil)
@parent = options.delete(:parent) # parent Container (i.e. flow/stack)

View File

@@ -33,6 +33,9 @@ module CyberarmEngine
@pending_element_recalculate_requests = []
@hit_elements = []
@touch_down_position = {}
@min_touch_drag_distance = 50
@needs_repaint = false
@menu = nil
@@ -227,6 +230,31 @@ module CyberarmEngine
@page&.update
end
def touch_began(touch)
@touch_down_position[touch.id] = touch
end
def touch_moved(touch)
# TODO: Support scrolling containers and dragging elements
end
def touch_ended(touch)
touch_down_pos = @touch_down_position[touch.id]
distance = Vector.new(touch_down_pos.x, touch_down_pos.y).distance(Vector.new(touch.x, touch.y))
# emulate mouse click
if distance < @min_touch_drag_distance
button_down(Gosu::MS_LEFT)
button_up(Gosu::MS_LEFT)
end
@touch_down_position.delete(touch.id)
end
def touch_cancelled(touch)
@touch_down_position.delete(touch.id)
end
def button_down(id)
super

View File

@@ -1,4 +1,4 @@
module CyberarmEngine
NAME = "InDev".freeze
VERSION = "0.25.1".freeze
VERSION = "0.30.0".freeze
end

View File

@@ -143,6 +143,22 @@ module CyberarmEngine
current_state&.gamepad_disconnected(index)
end
def touch_began(touch)
current_state&.touch_began(touch)
end
def touch_moved(touch)
current_state&.touch_moved(touch)
end
def touch_ended(touch)
current_state&.touch_ended(touch)
end
def touch_cancelled(touch)
current_state&.touch_cancelled(touch)
end
def gain_focus
@has_focus = true