Compare commits

...

3 Commits

Author SHA1 Message Date
Jill bb4175d279
refactor code to use 2d vector structs
shouldn't change functionality hopefully? but cleaner code :)
2023-06-01 20:14:34 +03:00
Jill d198bcc5e6
little bit more charm to clicking them versus dragging them 2023-06-01 19:14:40 +03:00
Jill be3e13794e
volume setting 2023-06-01 16:36:10 +03:00
6 changed files with 305 additions and 62 deletions

View File

@ -1,8 +1,8 @@
class Funfriend::ChatterContext < Funfriend::WindowContext
getter renderer : TextRenderer
getter parent : WindowContext?
property parent_relative_pos : NamedTuple(x: Int32, y: Int32)
getter window_size : NamedTuple(width: Int32, height: Int32)
property parent_relative_pos : Vec2 = Vec2.zero
getter window_size : Vec2
property timer : Float64
WINDOW_SIZE = {width: 256, height: 32}
@ -10,19 +10,16 @@ class Funfriend::ChatterContext < Funfriend::WindowContext
PADDING = 10
def initialize(text : String, position : NamedTuple(x: Int32, y: Int32), duration : Float64 = DEFAULT_DURATION, parent : WindowContext? = nil)
def initialize(text : String, position : Vec2, duration : Float64 = DEFAULT_DURATION, parent : WindowContext? = nil)
sheet = FontMan.parse_bm(File.read "assets/fonts/SpaceMono.fnt")
position_data = FontMan.position_text(text, sheet)
@window_size = {
width: position_data[:width] + PADDING * 2,
height: position_data[:height] + PADDING * 2
}
@window_size = Vec2.new(position_data[:width], position_data[:height]) + PADDING * 2
super(
title: "??__FUNFRIEND__?? > CHATTER",
width: window_size[:width], height: window_size[:height],
width: window_size.x_i, height: window_size.y_i,
transparent: false
)
@ -30,31 +27,20 @@ class Funfriend::ChatterContext < Funfriend::WindowContext
window.make_context_current
@timer = duration
@renderer = TextRenderer.new(text, sheet, window_size[:width], window_size[:height])
@renderer = TextRenderer.new(text, sheet, window_size.x_i, window_size.y_i)
window.position = {
x: position[:x] - window_size[:width]//2,
y: position[:y] - window_size[:height]//2,
}
window.position = (position - window_size / 2).xy_i
@parent = parent
if parent
@parent_relative_pos = {
x: position[:x] - (parent.window.position[:x] + parent.window.size[:width] // 2),
y: position[:y] - (parent.window.position[:y] + parent.window.size[:height] // 2),
}
else
@parent_relative_pos = {x: 0, y: 0}
@parent_relative_pos = position - (Vec2.new(parent.window.position) + Vec2.new(parent.window.size) / 2)
end
end
def update_position
if parent
p = parent.not_nil!
window.position = {
x: p.window.position[:x] + p.window.size[:width] // 2 + parent_relative_pos[:x] - window_size[:width] // 2,
y: p.window.position[:y] + p.window.size[:height] // 2 + parent_relative_pos[:y] - window_size[:height] // 2,
}
window.position = (Vec2.new(p.window.position) + Vec2.new(p.window.size) / 2 + parent_relative_pos - window_size / 2).xy_i
end
end
@ -82,10 +68,7 @@ class Funfriend::ChatterContext < Funfriend::WindowContext
end
def bump
@parent_relative_pos = {
x: parent_relative_pos[:x],
y: parent_relative_pos[:y] - window_size[:height] - 10
}
@parent_relative_pos.y -= window_size.y + 10
update_position
end

View File

@ -8,13 +8,16 @@ module Funfriend::ConfigMan
CONFIG_NAME = "cfg.ini"
alias ConfigValue = String | Int32 | Bool
alias ConfigValue = String | Int32 | Bool | Float64
alias ConfigSection = Hash(String, ConfigValue)
alias Config = Hash(String, ConfigSection)
DEFAULT_CONFIG = {
"window" => {
"funfriend_size" => 64.as(ConfigValue)
},
"sound" => {
"volume" => 0.2.as(ConfigValue)
}
}.as(Config)
@ -59,8 +62,9 @@ module Funfriend::ConfigMan
when String
value
when Int32
puts "huh"
value.to_i32?
when Float64
value.to_f64?
when Bool
value === "1" || value.downcase === "true"
end
@ -70,9 +74,13 @@ module Funfriend::ConfigMan
# write to config
@@config[sect_i][key] = casted_val.not_nil!
end
else
# keep as a string just incase it's ever needed
@@config[sect_i][key] = value
end
end
end
File.write(config_file, INI.build(@@config))
end
@@config_initialized = true

View File

@ -5,6 +5,7 @@ require "crystimage"
require "./log.cr"
require "./ease.cr"
require "./vec2.cr"
require "./gl.cr"
require "./configman.cr"
require "./textureman.cr"

View File

@ -8,20 +8,32 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
["INTERLOPER!", "WELCOME", "BUT ALSO PLEASE DO NOT BOTHER ME", "VERY BUSY"]
]
STAY_ARRAY = [
"OK I'LL BE HERE"
]
TOUCH_ARRAY = [
"HI INTERLOPER!",
"HELLO!",
"HI!"
]
getter renderer : FunfriendRenderer
property chatter_timer : Float64 = 1.0
property chatter_index : Int32 = 0
property chatter_array : Array(String)? = CHATTER_ARRAY.sample
property held : Bool = false
property held_at : NamedTuple(x: Int32, y: Int32) = {x: 0, y: 0}
property held_at : Vec2 = Vec2.zero
property started_holding_at : Vec2 = Vec2.zero
STAY_STILL_AFTER_HELD = 1.0
property held_timer : Float64 = 0.0
property waiting_for_stable_pos : Bool = false
property static_pos : NamedTuple(x: Int32, y: Int32)
property static_pos : Vec2
property easing_from : NamedTuple(x: Int32, y: Int32) = {x: 0, y: 0}
property easing_to : NamedTuple(x: Int32, y: Int32) = {x: 0, y: 0}
property easing_from : Vec2 = Vec2.zero
property easing_to : Vec2 = Vec2.zero
property easing_dur : Float64 = 0.0
property easing_t : Float64 = 0.0
@ -31,7 +43,7 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
def initialize
super(
title: "??_FUNFRIEND_??",
width: window_size[:width], height: window_size[:height],
width: window_size.x_i, height: window_size.y_i,
transparent: true
)
@ -48,10 +60,14 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
SoundMan.play_sound("assets/sfx/talk#{(1..8).sample}.ogg")
@easing_dur = 0.0
@held = true
@held_at = {
@held_at = Vec2.new({
x: window.cursor.position[:x].to_i,
y: window.cursor.position[:y].to_i,
}
})
if @held_timer <= 0
@started_holding_at = Vec2.new(window.position)
LOG.info { "starting holding at #{started_holding_at}" }
end
@held_timer = STAY_STILL_AFTER_HELD
window.cursor Window::Cursor::Shape::Hand
elsif event.action.release?
@ -74,12 +90,12 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
y: monitor.position[:y] + (monitor.video_mode.size[:height] * rand((0.0..1.0))).to_i
}
window.position = random_pos
@static_pos = random_pos
@static_pos = Vec2.new(random_pos)
end
def window_size
funfriend_size = ConfigMan.config["window"]["funfriend_size"].as(Int32)
{width: (funfriend_size * 1.3).to_i, height: (funfriend_size * 1.3).to_i}
Vec2.new((funfriend_size * 1.3).floor)
end
def render(dt : Float64)
@ -87,51 +103,101 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
window.make_context_current
# draw funfriend
renderer.render(dt, window_size[:width], window_size[:height])
renderer.render(dt, window_size.x_i, window_size.y_i)
end
def goto(pos : NamedTuple(x: Int32, y: Int32), dur : Float64)
def goto(pos : Vec2, dur : Float64, set_as_static : Bool = true)
@easing_t = 0.0
@easing_dur = dur
@easing_from = window.position
@easing_from = Vec2.new(window.position)
@easing_to = pos
if set_as_static
@static_pos = @easing_to
end
LOG.info { "going from #{easing_from} to #{easing_to}" }
end
enum Behavior
Wander
Follow
Stay
end
FOLLOW_DIST = 120
def behavior : Behavior
speaking ? Behavior::Follow : Behavior::Wander
end
def moving
@easing_dur != 0.0 && @easing_t <= @easing_dur
end
def update_wander(dt : Float64)
if @easing_dur != 0.0 && @easing_t <= @easing_dur
if moving
@easing_t = @easing_t + dt
a = Ease.inOutSine(@easing_t / @easing_dur)
window.position = {
x: (@easing_from[:x] * (1.0 - a) + @easing_to[:x] * a).to_i,
y: (@easing_from[:y] * (1.0 - a) + @easing_to[:y] * a).to_i,
}
window.position = (@easing_from * (1.0 - a) + @easing_to * a).xy_i
@wander_timer = WANDER_TIMER
else
@wander_timer = @wander_timer - dt
if @wander_timer <= 0
goto({
x: @static_pos[:x] + (-40 .. 40).sample,
y: @static_pos[:y] + (-40 .. 40).sample,
}, 4.0)
case behavior
when .wander?
@wander_timer = @wander_timer - dt
if @wander_timer <= 0
goto(@static_pos + Vec2.rand((0 .. 40)), 4.0, set_as_static: false)
end
when .follow?
if !moving
x_dist = window.cursor.position[:x]
y_dist = window.cursor.position[:y]
x_target = window.position[:x].to_f
y_target = window.position[:y].to_f
if x_dist.abs > FOLLOW_DIST
x_target = window.position[:x] + x_dist - FOLLOW_DIST * x_dist.sign
end
if y_dist.abs > FOLLOW_DIST
y_target = window.position[:y] + y_dist - FOLLOW_DIST * y_dist.sign
end
goto(Vec2.new(x_target, y_target), 1.0)
end
end
end
end
def update_pos(dt : Float64)
if held
window.position = {
x: window.position[:x] - held_at[:x] + window.cursor.position[:x].to_i,
y: window.position[:y] - held_at[:y] + window.cursor.position[:y].to_i,
}
@static_pos = window.position
@static_pos = Vec2.new(window.position) - held_at + Vec2.new(window.cursor.position)
window.position = @static_pos.xy_i
else
@held_timer = @held_timer - dt
if @held_timer <= 0
update_wander(dt)
if @waiting_for_stable_pos
@waiting_for_stable_pos = false
stable_pos_dist = @static_pos.dist(@started_holding_at)
LOG.info { "travelled #{stable_pos_dist}" }
if !speaking
if stable_pos_dist > 50
# moved quite a bit from initial point
say STAY_ARRAY.sample
else
# just touched
say TOUCH_ARRAY.sample
end
end
end
else
@waiting_for_stable_pos = true
end
end
end
@ -143,14 +209,18 @@ class Funfriend::FunfriendContext < Funfriend::WindowContext
end
end
Funfriend.add_context(ChatterContext.new(text, {
x: window.position[:x] + window_size[:width] // 2,
y: window.position[:y] - 20
}, parent: self))
Funfriend.add_context(ChatterContext.new(text, Vec2.new(
window.position[:x] + window_size.x / 2,
window.position[:y] - 20
), parent: self))
SoundMan.play_sound("assets/sfx/talk#{(1..8).sample}.ogg")
end
def speaking
@chatter_array && @chatter_index < @chatter_array.not_nil!.size
end
def update(dt : Float64)
@chatter_timer = @chatter_timer - dt
if @chatter_timer <= 0.0

View File

@ -20,6 +20,6 @@ module Funfriend::SoundMan
SDL::Mix.init(SDL::Mix::Init::OGG); at_exit { SDL::Mix.quit }
SDL::Mix.open
SDL::Mix::Channel.volume = SDL::Mix::MAX_VOLUME // 5
SDL::Mix::Channel.volume = SDL::Mix::MAX_VOLUME * ConfigMan.config["sound"]["volume"].as(Float64)
end
end

181
src/vec2.cr Normal file
View File

@ -0,0 +1,181 @@
require "math"
include Math
module Funfriend
struct Vec2
property x : Float64
property y : Float64
def initialize(@x = 0.0, @y = 0.0)
end
def initialize(xy : Int32)
@x = @y = xy
end
def initialize(xy : Float64)
@x = @y = xy
end
def initialize(xy : NamedTuple(x: Int32, y: Int32))
initialize(xy[:x], xy[:y])
end
def initialize(xy : NamedTuple(x: Float64, y: Float64))
initialize(xy[:x], xy[:y])
end
def initialize(wh : NamedTuple(width: Int32, height: Int32))
initialize(wh[:width], wh[:height])
end
# swizzles
def xy
{x: @x, y: @y}
end
def yx
{x: @y, y: @x}
end
# int swizzles
def xy_i
{x: @x.to_i, y: @y.to_i}
end
def yx_i
{x: @y.to_i, y: @x.to_i}
end
def x_i
x.to_i
end
def y_i
y.to_i
end
# simpler swizzle
def values
{x, y}
end
def dot(other : Vec2)
x * other.x + y * other.y
end
def cross(other : Vec2)
Vec2.new(
self.x * other.y - self.y * other.x,
self.y * other.x - self.x * other.y
)
end
def angle
atan2(y, x)
end
def len
sqrt(x ** 2 + y ** 2)
end
def square_len
x ** 2 + y ** 2
end
def angle(other : Vec2)
self ** other / (self.length * other.length)
end
def +(other : Vec2)
Vec2.new(self.x + other.x, self.y + other.y)
end
def +(other : Number)
Vec2.new(self.x + other, self.y + other)
end
def -(other : Vec2)
Vec2.new(self.x - other.x, self.y - other.y)
end
def -(other : Number)
Vec2.new(self.x - other, self.y - other)
end
def -
Vec2.new(-self.x, -self.y)
end
def *(other : Vec2)
Vec2.new(self.x * other.x, self.y * other.y)
end
def *(other : Number)
Vec2.new(self.x * other, self.y * other)
end
def /(other : Vec2)
Vec2.new(self.x / other.x, self.y / other.y)
end
def /(other : Number)
Vec2.new(self.x / other, self.y / other)
end
def clone
Vec2.new(self.x, self.y)
end
def normalize!
m = length
unless m == 0
inverse = 1.0 / m
self.x *= inverse
self.y *= inverse
end
self
end
def normalize
clone.normalize!
end
def scale!(scale : Float64)
normalize!
self.x *= scale
self.y *= scale
end
def scale(scale : Float64)
clone.scale!(scale)
end
def dist(other : Vec2)
(self - other).len
end
def square_dist(other : Vec2)
(self - other).square_len
end
def ==(other : Vec2)
self.x == other.x && self.y == other.y
end
def !=(other : Vec2)
self.x != other.x || self.y != other.y
end
def to_s
"(#{x}, #{y})"
end
def inspect
"(x: #{x.inspect}, y: #{y.inspect})"
end
def self.zero
Vec2.new(0.0)
end
def self.additive_identity
Vec2.new(0.0)
end
def self.multiplicative_identity
Vec2.new(1.0)
end
def self.from_polar(angle : Float64, length : Float64)
Vec2.new(cos(angle) * length, sin(angle) * length)
end
def self.rand(length : Range = (0 .. 1))
from_polar((0..360).sample, length.sample)
end
end
end