From: Brendan Hansen Date: Thu, 17 Mar 2022 18:54:21 +0000 (-0500) Subject: added path finding and collision mask X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=45bc1c19916a8f44fd8a44cae7cc9f5fb5768f41;p=bar-game.git added path finding and collision mask --- diff --git a/docs/todo.txt b/docs/todo.txt index 02a65ad..fccef98 100644 --- a/docs/todo.txt +++ b/docs/todo.txt @@ -11,3 +11,8 @@ customizable behavior, without having to change the code. * More items to order +* Reputation = likelihood someone walks in + +* Waves / Continuous flow + +* Purchase bar-upgrades between days \ No newline at end of file diff --git a/run_tree/scenes/level1.scene b/run_tree/scenes/level1.scene index 98bb918..15f8b37 100644 --- a/run_tree/scenes/level1.scene +++ b/run_tree/scenes/level1.scene @@ -1,3 +1,13 @@ +[Custom] +id = 342 +flags = 0 +pos = 0 0 +size = 8 8 +:CollisionMaskComponent +should_render=false +:RenderComponent +layer=1000 + [Background] id = 100 flags = 0 @@ -1420,7 +1430,7 @@ size.x = 16.0000 size.y = 32.0000 :FurnitureComponent furniture_type = 0 -taken = true +taken = false :SpriteRenderComponent sprite.sheet = "./assets/images/spritesheet.png" sprite.pos.x = 48.0000 @@ -1603,7 +1613,7 @@ draw_item = true [Custom] id = 334 -flags = 1 +flags = 3 pos.x = 176.0000 pos.y = 32.0000 size.x = 32.0000 diff --git a/src/entity/components/collision_mask.onyx b/src/entity/components/collision_mask.onyx new file mode 100644 index 0000000..2b4c7da --- /dev/null +++ b/src/entity/components/collision_mask.onyx @@ -0,0 +1,153 @@ + +use package core + +CollisionMaskComponent :: struct { + use component: Component; + + grid_size: i32 = 16; + width: i32 = 50; + height: i32 = 40; + + #tag Entity_Store.Skip, Editor_Hidden + mask: [] bool; + + #tag Entity_Store.Skip + should_render := false; + + added :: (use this: ^CollisionMaskComponent, entity: ^Entity) { + scene->modify_component(entity, RenderComponent) { + comp.func = render; + } + } + + update :: (use this: ^CollisionMaskComponent, entity: ^Entity, dt: f32) { + if mask.data == null { + memory.alloc_slice(^mask, width * height); + } + + for y: height { + for x: width { + mask[y * width + x] = false; + for scene.entities { + if it.flags & .Solid { + area := Rect.{ ~~(x * grid_size), ~~(y * grid_size), ~~grid_size, ~~grid_size }; + if Rect.intersects(Entity.get_rect(it), area) { + mask[y * width + x] = true; + continue continue; + } + } + } + } + } + } + + get_mask :: (use this: ^CollisionMaskComponent, pos: Vector2) -> bool { + x := ~~pos.x / grid_size; + y := ~~pos.y / grid_size; + if x < 0 || x >= width || y < 0 || y >= height do return false; + return mask[y * width + x]; + } + + get_path :: (use this: ^CollisionMaskComponent, start: Vector2, target: Vector2, buf: ^[..] Vector2) -> bool { + Node :: struct { + pos: Vector2i; + g: f32 = 0; + h: f32 = 0; + } + + Visited_Node :: struct { + prev: Vector2i; + cost: f32; + } + + start_pos := Vector2i.{ ~~start.x / grid_size, ~~start.y / grid_size }; + target_pos := Vector2i.{ ~~target.x / grid_size, ~~target.y / grid_size }; + + stack: [..] Node; + stack << .{ start_pos }; + + visited: Map(Vector2i, Visited_Node); + found := false; + + while stack.count != 0 { + top := array.pop(^stack); + + if top.pos == target_pos { + found = true; + break; + } + + for dx: -1 .. 2 { + for dy: -1 .. 2 { + if dx == 0 && dy == 0 do continue; + + tx := top.pos.x + dx; + ty := top.pos.y + dy; + if tx < 0 || tx >= width || ty < 0 || ty >= height do continue; + + if dx != 0 && dy != 0 { + if mask[top.pos.y * width + tx] || mask[ty * width + top.pos.x] do continue; + } + + if !mask[ty * width + tx] || target_pos == .{tx, ty} { + if array.contains(stack, #(it.pos == .{tx, ty})) do continue; + + g := cast(f32) (math.abs(dx) + math.abs(dy)) + top.g; + h := cast(f32) (math.abs(tx - start_pos.x) + math.abs(ty - start_pos.y)); + f := g + h; + + if visited->has(.{ tx, ty }) { + vn := ^visited[.{ tx, ty }]; + if vn.cost > f { + *vn = .{ top.pos, f }; + } + + } else { + stack << .{ .{tx, ty}, g, h, }; + visited[.{ tx, ty }] = .{ top.pos, f }; + } + } + } + } + + array.quicksort(stack, (n1: ^Node, n2: typeof n1) => { + f1 := n1.g + n1.h; + f2 := n2.g + n2.h; + if f1 < f2 do return 1; + if f2 < f1 do return -1; + return 0; + }); + } + + if found { + path_pos := target_pos; + while visited->has(path_pos) { + *buf << .{ + ~~(path_pos.x * grid_size), + ~~(path_pos.y * grid_size), + }; + path_pos = visited[path_pos].prev; + } + + array.reverse(*buf); + } + + return found; + } + + render :: (entity: ^Entity) { + cmc := entity->get(CollisionMaskComponent); + + if !cmc.should_render do return; + + gs := cmc.grid_size; + immediate_set_color(.{0.3, 0.3, 0.9, 0.4}); + for y: cmc.height { + for x: cmc.width { + if cmc.mask[y * cmc.width + x] { + immediate_rectangle(~~(x * gs), ~~(y * gs), ~~gs, ~~gs); + } + } + } + } +} \ No newline at end of file diff --git a/src/entity/components/patron.onyx b/src/entity/components/patron.onyx index 7ed1ea8..eff116a 100644 --- a/src/entity/components/patron.onyx +++ b/src/entity/components/patron.onyx @@ -21,6 +21,11 @@ PatronComponent :: struct { annoy_timeout: f32; walk_speed: f32 = 100.0f; + #tag Editor_Hidden, Entity_Store.Skip + path: [..] Vector2; + #tag Editor_Hidden, Entity_Store.Skip + path_pos: i32; + init :: (use this: ^PatronComponent) { order_item = random.choice(item_store.items.entries).key; } @@ -120,17 +125,43 @@ PatronComponent :: struct { } } - if target != Entity_Nothing { - target_entity := scene->get(target); - target_location := target_entity.pos; - if Vector2.square_mag(target_location - entity.pos) < 16 { - entity.pos = target_location; + { + if target != Entity_Nothing { + target_entity := scene->get(target); + target_location := target_entity.pos; + if Vector2.square_mag(target_location - entity.pos) < 4 { + break; + } + + if path.count == 0 { + array.clear(^path); + collision_mask := scene->first_component(CollisionMaskComponent); + if !collision_mask->get_path(entity.pos, target_location, ^path) { + debug_log(.Warning, "No path for patron."); + return; + } + + path_pos = 0; + } + + target_position := path[path_pos]; + if Vector2.square_mag(target_position - entity.pos) < 16 { + path_pos += 1; + } - if state == .Walking_To_Seat do state = .Waiting_To_Place_Order; - if state == .Leaving do entity.flags |= .Dead; - } else { - delta := Vector2.norm(target_location - entity.pos) * walk_speed; + delta := Vector2.norm(target_position - entity.pos) * walk_speed; entity.pos += delta * dt; + + if path_pos >= path.count { + array.clear(^path); + + target_entity := scene->get(target); + target_location := target_entity.pos; + entity.pos = target_location; + + if state == .Walking_To_Seat do state = .Waiting_To_Place_Order; + if state == .Leaving do entity.flags |= .Dead; + } } } @@ -211,5 +242,16 @@ PatronComponent :: struct { item_data := item_store->get_item(order_item); item_data.sprite->render(r); } + + // + // Draw paths for debugging + #if false { + if path.count > 0 { + immediate_set_color(.{1, 1, 0, 0.4}); + for p: path { + immediate_rectangle(p.x, p.y, 8, 8); + } + } + } } } diff --git a/src/entity/editor.onyx b/src/entity/editor.onyx index da6d3a5..ac860f9 100644 --- a/src/entity/editor.onyx +++ b/src/entity/editor.onyx @@ -25,6 +25,7 @@ editor_init :: () { conv.format(^save_path, "./scenes/level1.scene"); custom_editors[Color] = render_color_picker; + custom_editors[bool] = render_bool_toggle; } editor_shown :: () => editor_openness != 0.0f || editor_target_openness != 0.0f; @@ -591,6 +592,13 @@ editor_draw :: () { immediate_rectangle(x, y+120, w, h - 120); } +#local render_bool_toggle :: (v: any, x, y, w, h: f32, field_name: str) { + immediate_set_color(.{.3, .3, .3}); + immediate_rectangle(x, y, w, h); + + draw_checkbox(.{x, y + 64, w, 48}, cast(^bool) v.data, field_name); +} + #local { background_color :: Color.{0.15, 0.15, 0.15, 1}; diff --git a/src/entity/scene.onyx b/src/entity/scene.onyx index b8f87ae..55dc5f3 100644 --- a/src/entity/scene.onyx +++ b/src/entity/scene.onyx @@ -19,27 +19,27 @@ Entity_ID :: #distinct u32 // // 'init' is called when the component is first created, before it // has been added to any entity. - init : (rawptr) -> void = null_proc; + init : (^Component) -> void = null_proc; // // 'added' is called when the component is added to an entity. // In theory, it could be called multiple times, if the component // is shared between multiple entities. - added : (rawptr, ^Entity) -> void = null_proc; + added : (^Component, ^Entity) -> void = null_proc; // // 'update' is called every update cycle. Currently, there is no // specified as to the order in which components get updated. // I think by circumstance, it is the order that you add them // to the entity, but could break in the future. - update : (rawptr, ^Entity, f32) -> void = null_proc; + update : (^Component, ^Entity, f32) -> void = null_proc; // // 'post_render' is called after the entity has been rendered // normally. Entities must have a 'RenderComponent' in order // to have 'post_render' called on their components. This // can be used to add overlays or popups to an entity. - post_render: (rawptr, ^Entity) -> void = null_proc; + post_render: (^Component, ^Entity) -> void = null_proc; } Component :: struct { diff --git a/src/utils/vecmath.onyx b/src/utils/vecmath.onyx index 1ba7aa8..c3e02e3 100644 --- a/src/utils/vecmath.onyx +++ b/src/utils/vecmath.onyx @@ -1,3 +1,12 @@ +#local hash :: package core.hash + +Vector2i :: struct { + x, y: i32; + + #struct_tag conv.Custom_Format.{format_vector2i} + #struct_tag conv.Custom_Parse.{parse_vector2i} +} + Vector2 :: struct { x, y: f32; @@ -57,6 +66,13 @@ Vector3 :: struct { #struct_tag conv.Custom_Format.{format_vector3} } +#operator + macro (v1, v2: Vector2i) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y }; +#operator - macro (v1, v2: Vector2i) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y }; +#operator * macro (v: Vector2i, s: i32) => (typeof v ).{ v.x * s, v.y * s }; +#operator * macro (v1, v2: Vector2i) => (typeof v1).{ v1.x * v2.x, v1.y * v2.y }; +#operator == macro (v1, v2: Vector2i) => v1.x == v2.x && v1.y == v2.y; +#match hash.to_u32 macro (v: Vector2i) => 13 * v.x + 17 * v.y; + #operator + macro (v1, v2: Vector2) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y }; #operator - macro (v1, v2: Vector2) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y }; #operator * macro (v: Vector2, s: f32) => (typeof v ).{ v.x * s, v.y * s }; @@ -78,6 +94,10 @@ Vector3 :: struct { #local { conv :: package core.conv + format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) { + conv.format(output, "({}, {})", v.x, v.y); + } + format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) { conv.format(output, "({}, {})", v.x, v.y); } @@ -90,6 +110,21 @@ Vector3 :: struct { conv.format(output, "({}, {}, {})", v.x, v.y, v.z); } + parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool { + string :: package core.string + + line := line_; + xs := string.read_until(^line, #char " "); + string.advance(^line, 1); + ys := string.read_until(^line, #char " "); + + if xs == "" || ys == "" do return false; + + output.x = ~~ conv.str_to_i64(xs); + output.y = ~~ conv.str_to_i64(ys); + return true; + } + parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool { string :: package core.string @@ -112,10 +147,10 @@ Rect :: struct { x, y, w, h: f32; intersects :: (r1, r2: Rect) -> bool { - return r1.x <= r2.x + r2.w - && r1.x + r1.w >= r2.x - && r1.y <= r2.y + r2.h - && r1.y + r1.h >= r2.y; + return r1.x < r2.x + r2.w + && r1.x + r1.w > r2.x + && r1.y < r2.y + r2.h + && r1.y + r1.h > r2.y; } contains :: (r: Rect, p: Vector2) -> bool {