Huge refactor, Adding more code to engine from the game

This commit is contained in:
sam
2026-03-19 22:32:26 +05:45
parent 493338d91b
commit 09941e3e1d
10 changed files with 799 additions and 39 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
package ion package edit2d
import "core:strings"
import "base:runtime" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:math" import "core:math"
+61 -4
View File
@@ -1,19 +1,23 @@
package ion package edit2d
import "base:intrinsics"
import "core:encoding/cbor" import "core:encoding/cbor"
import "base:runtime"
import im "shared:odin-imgui" import im "shared:odin-imgui"
import "shared:odin-imgui/imgui_impl_glfw" import "shared:odin-imgui/imgui_impl_glfw"
import "shared:odin-imgui/imgui_impl_opengl3" import "shared:odin-imgui/imgui_impl_opengl3"
import gl "vendor:OpenGL" import gl "vendor:OpenGL"
import "core:fmt"
import "vendor:glfw" import "vendor:glfw"
import "core:reflect"
engine_state :: struct engine_state :: struct
{ {
window : glfw.WindowHandle, window : glfw.WindowHandle,
draw : Draw, draw : Draw,
restart, pause : bool, restart, pause : bool,
substep_count : u32, substep_count : u32,
@@ -25,6 +29,7 @@ engine_state :: struct
drop_callback : glfw.DropProc, drop_callback : glfw.DropProc,
input : input_state, input : input_state,
} }
MAX_KEYS :: 512 MAX_KEYS :: 512
@@ -38,13 +43,64 @@ input_state :: struct
curr, prev : [MAX_KEYS]bool, curr, prev : [MAX_KEYS]bool,
} }
//This function defines if the game's structs are defines as requirements
//Doing the checking in the beggining makes writing helper code more reliable
engine_check_types :: proc($Game: typeid)
{
assert(intrinsics.type_has_field(Game, "levels"), "game should contan levels")
assert(intrinsics.type_has_field(Game, "engine"), "game should contan engine")
level_type := reflect.struct_field_by_name(Game, "levels")
engine_type := reflect.struct_field_by_name(Game, "engine")
assert(engine_type.is_using, "You should be using the engine")
assert(engine_type.type.id == typeid_of(engine_game), "Engine Should be engine_game")
level_variant := level_type.type.variant
assert(reflect.is_dynamic_map(level_type.type), "Level should be map of names and level files")
level_map_type := level_type.type.variant.(runtime.Type_Info_Map)
assert(level_map_type.key.id == typeid_of(string), "Level key should be name")
//Level should also have engine
level_engine_type := reflect.struct_field_by_name(level_map_type.value.id, "engine")
assert(level_engine_type.is_using, "Should be using engine")
assert(level_engine_type.type.id == typeid_of(engine_world), "Should be using engine")
entities_type := reflect.struct_field_by_name(level_map_type.value.id, "entities")
entity_defs_type := reflect.struct_field_by_name(level_map_type.value.id, "entity_defs")
assert(reflect.is_dynamic_array(entities_type.type))
assert(reflect.is_dynamic_array(entity_defs_type.type))
entities_arr := entities_type.type.variant.(runtime.Type_Info_Dynamic_Array)
entity_defs_arr := entity_defs_type.type.variant.(runtime.Type_Info_Dynamic_Array)
entity_engine := reflect.struct_field_by_name(entities_arr.elem.id, "engine")
defs_engine := reflect.struct_field_by_name(entity_defs_arr.elem.id, "engine")
assert(entity_engine.is_using, "Should be using")
assert(defs_engine.is_using, "Should be using")
assert(entity_engine.type.id == typeid_of(engine_entity))
assert(defs_engine.type.id == typeid_of(engine_entity_def))
}
/* /*
This will only be called once to initilize the engine This will only be called once to initilize the engine
initilize graphics library, glfw, callbacks initilize graphics library, glfw, callbacks
*/ */
engine_init :: proc(state: ^engine_state) engine_init :: proc($GameType : typeid, state: ^engine_state)
{ {
engine_check_types(GameType)
assert(glfw.Init() == true) assert(glfw.Init() == true)
glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1) glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1)
@@ -105,6 +161,7 @@ engine_init :: proc(state: ^engine_state)
} }
update_frame :: proc(state: ^engine_state) update_frame :: proc(state: ^engine_state)
{ {
state.input.mouse_wheel = {} state.input.mouse_wheel = {}
+52 -21
View File
@@ -1,4 +1,4 @@
package ion package edit2d
import b2 "vendor:box2d" import b2 "vendor:box2d"
import "core:fmt" import "core:fmt"
@@ -12,30 +12,13 @@ static_index_global :: struct
offset : b2.Vec2, offset : b2.Vec2,
} }
//DropProc :: #type proc "c" (window: WindowHandle, count: c.int, paths: [^]cstring)
/* /*
This file contains code to handle box2d stuffs of the game code This file contains code to handle box2d stuffs of the game code
Don't put game's logic here Don't put game's logic here
*/ */
engine_world :: struct
{
world_id : b2.WorldId,
//This in engine code?
static_indexes : map[static_index]int `cbor:"-"`,
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
relations_serializeable : map[ static_index][dynamic]static_index_global,
/*
Seems okay to put the joint defs in engine rather than in game because
we don't add more attributes in joints in the game
Can be changed later without requireing refactor
*/
revolute_joint_defs : [dynamic]revolt_joint_def,
distant_joint_defs : [dynamic]distance_joint_def,
joints : [dynamic]b2.JointId `cbor:"-"`,
}
engine_entity_flags_enum :: enum u64 { engine_entity_flags_enum :: enum u64 {
POLYGON_IS_BOX, POLYGON_IS_BOX,
@@ -75,6 +58,32 @@ engine_entity_def :: struct {
link_length_array : [dynamic]b2.Vec2, link_length_array : [dynamic]b2.Vec2,
} }
default_engine_entity_def :: proc() -> engine_entity_def
{
ret : engine_entity_def
ret.body_def = b2.DefaultBodyDef()
ret.shape_def = b2.DefaultShapeDef()
ret.shape_type = .polygonShape
ret.scale = 1
ret.centers = {{-10, 0}, {10, 0}}
ret.size = {2, 2}
ret.radius = 10
ret.body_count = 10
ret.rev_joint = b2.DefaultRevoluteJointDef()
//for dynamic polygon
vs : [4]b2.Vec2 = {
{-1.0, -1.0},
{-1.0, 1.0},
{1.0, 1.0},
{1.0, -1.0},
}
ret.is_loop = true
for v in vs do append(&ret.vertices, v)
return ret
}
compare_engine_entity_def :: proc(a, b : engine_entity_def) -> bool compare_engine_entity_def :: proc(a, b : engine_entity_def) -> bool
{ {
@@ -118,6 +127,7 @@ engine_entity :: struct {
joints : [dynamic]b2.JointId, joints : [dynamic]b2.JointId,
entity_flags : engine_entity_flags, entity_flags : engine_entity_flags,
index : ^static_index, index : ^static_index,
joint_id : b2.JointId,
} }
@@ -259,7 +269,11 @@ engine_entity_single_body :: proc(def : ^engine_entity_def, world_id: b2.WorldId
return new_entity return new_entity
} }
engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId, index : i32) -> engine_entity engine_create_chain_shape :: proc(
def : ^engine_entity_def,
world_id : b2.WorldId,
index : i32
) -> engine_entity
{ {
joint_def := def.rev_joint joint_def := def.rev_joint
orig_pos := def.body_def.position orig_pos := def.body_def.position
@@ -296,6 +310,23 @@ engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId
} }
engine_create_entity :: proc(
def : ^engine_entity_def,
world_id : b2.WorldId,
index : i32
) -> engine_entity
{
if .CHAIN not_in def.entity_flags
{
return engine_entity_single_body(def, world_id, index)
}
else
{
return engine_create_chain_shape(def, world_id, index)
}
}
+14
View File
@@ -0,0 +1,14 @@
package edit2d
game_mode :: enum
{
PLAY,
EDIT,
}
engine_game :: struct {
curr_level : string,
interface : interface_state,
mode : game_mode,
}
+221
View File
@@ -0,0 +1,221 @@
package edit2d
import b2 "vendor:box2d"
import im "shared:odin-imgui"
import "vendor:glfw"
import "base:runtime"
/*
Handling input
Key Bindings
Meta keys
E -> Cycle thru interface edit mode
CTRL + S -> Save level
CTRL + C -> Copy entity
CTRL + V -> Paste entity
ALT + D -> Delete entity
CTRL + Mouse left -> Move according to edit mode
Mouse wheel -> Scale according to edit mode
Mouse Left -> select entity
Mouse Right -> create new entity
TODO:
Command mode?
Different handling of keys for different modes
*/
/*
Handle all keys when the editor is in entity mode
Select
Add
Move
Delete
Copy paste
Resize
*/
handle_entity_mode :: proc(
$E : typeid,
state : ^engine_state,
game_data : ^$G,
) -> bool
{
ret := false
click_query_filter :: proc "c" (
shape_id : b2.ShapeId,
ctx : rawptr
) -> bool
{
context = runtime.default_context()
game := cast(^engine_game)ctx
index := i32(uintptr(b2.Shape_GetUserData(shape_id)))
game.interface.selected_entity = index
return true
}
game: ^engine_game = game_data
interface := &game.interface
level := &game_data.levels[game.curr_level]
input := &state.input
mpos := camera_convert_screen_to_world(&state.draw.cam, input.mouse)
//Setlect entity
if is_key_pressed(state, glfw.MOUSE_BUTTON_LEFT)
{
aabb : b2.AABB = {mpos, mpos + 1}
r := b2.World_OverlapAABB(
level.engine.world_id,
aabb,
b2.DefaultQueryFilter(),
click_query_filter,
game,
)
}
else if is_key_pressed(state, glfw.MOUSE_BUTTON_RIGHT)
{
new_def := E {
engine = default_engine_entity_def(),
type = .NPC
}
interface.selected_entity = i32(len(level.entity_defs))
append(&level.entity_defs, new_def)
ret = true
}
else{
//Can only do these actions if a entity is selected
if interface.selected_entity != -1 && len(level.entity_defs) > 0
{
def := &level.entity_defs[interface.selected_entity]
//Copy paste
if is_key_down( state, glfw.KEY_LEFT_CONTROL)\\
&& is_key_pressed( state, glfw.KEY_C)
{
game_data.copied_def = def^
}
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
&& is_key_pressed( state, glfw.KEY_V)
{
//Paste the copied def on the mouse position
interface.selected_entity = i32(len(level.entity_defs))
game_data.copied_def.body_def.position = mpos
append(&level.entity_defs, game_data.copied_def)
ret = true
}
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
&& is_key_pressed( state, glfw.KEY_D)
{
//Remove the def
unordered_remove(&level.entity_defs, interface.selected_entity)
interface.selected_entity = -1
ret = true
}
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
&& is_key_down( state, glfw.MOUSE_BUTTON_LEFT)
{
//move def
def.body_def.position = mpos
ret = true
}
else if state.input.mouse_wheel.y != 0
{
def.scale += f32(state.input.mouse_prev.y / 5)
ret = true
}
}
}
return ret
}
handle_input :: proc(
$E : typeid,
state : ^engine_state,
game_data : ^$G,
) -> bool
{
game : ^engine_game = &game_data.engine
/*
Applies for all mode
*/
level := &game_data.levels[game.curr_level]
if is_key_pressed(state, glfw.KEY_E)
{
interface := &game.interface
if interface.edit_mode == max(EditMode)
{
interface.edit_mode = min(EditMode)
}else
{
new_val := int(interface.edit_mode) + 1
interface.edit_mode = EditMode(new_val)
}
}
if is_key_pressed(state, glfw.KEY_S) && \\
is_key_down(state, glfw.KEY_LEFT_CONTROL)
{
level_save_to_file(level, state)
}
if im.GetIO().WantCaptureMouse do return false
if im.GetIO().WantCaptureKeyboard do return false
#partial switch game.interface.edit_mode
{
case .ENTITY : handle_entity_mode(E, state, game_data)
// case .VERTICES : interface_handle_vertices_mode(state, game_data)
// case .OVERVIEW : interface_handle_overview_mode(state, game_data)
// case .CHAIN : interface_handle_chain_mode(state, game_data)
// case .JOINT : interface_handle_joint_mode(state, game_data)
}
return false
}
+29 -5
View File
@@ -1,7 +1,6 @@
package ion package edit2d
import "core:slice"
import "core:fmt"
import im "shared:odin-imgui" import im "shared:odin-imgui"
import "core:fmt"
import b2 "vendor:box2d" import b2 "vendor:box2d"
/* /*
@@ -27,7 +26,7 @@ interface_state :: struct
{ {
entity_defs : [dynamic]^engine_entity_def, entity_defs : [dynamic]^engine_entity_def,
entities : [dynamic]^engine_entity, entities : [dynamic]^engine_entity,
selected_entity : ^i32, selected_entity : i32,
world : ^engine_world, world : ^engine_world,
state : ^engine_state, state : ^engine_state,
@@ -42,6 +41,15 @@ interface_state :: struct
curr_static_index : static_index_global, curr_static_index : static_index_global,
} }
interface_get_default :: proc(interface: ^interface_state)
{
interface.selected_entity = 0
interface.vertex_index = new(i32)
interface.vertex_index^ = 0
interface.chain_index = new(i32)
interface.chain_index^ = 0
}
interface_draw_options :: proc(state: ^engine_state) { interface_draw_options :: proc(state: ^engine_state) {
debug_draw := &state.draw.debug_draw debug_draw := &state.draw.debug_draw
@@ -66,6 +74,23 @@ interface_draw_options :: proc(state: ^engine_state) {
interface_all :: proc(interface: ^interface_state) -> bool interface_all :: proc(interface: ^interface_state) -> bool
{ {
ret := false ret := false
if interface.selected_entity <0
{
interface.selected_entity = 0
}
if im.Begin("Edit Mode", nil)
{
for type in EditMode
{
if im.RadioButton(fmt.ctprint(type), interface.edit_mode == type)
{
interface.edit_mode = type
}
}
}
im.End()
if im.Begin("Box2d interface") if im.Begin("Box2d interface")
{ {
if im.BeginTabBar("Tabs") if im.BeginTabBar("Tabs")
@@ -77,7 +102,6 @@ interface_all :: proc(interface: ^interface_state) -> bool
im.EndTabItem() im.EndTabItem()
} }
if im.BeginTabItem("Joints", nil, {}) if im.BeginTabItem("Joints", nil, {})
{ {
if interface_joints(interface) do ret = true if interface_joints(interface) do ret = true
+4 -4
View File
@@ -1,4 +1,4 @@
package ion package edit2d
import b2 "vendor:box2d" import b2 "vendor:box2d"
import im "shared:odin-imgui" import im "shared:odin-imgui"
@@ -62,7 +62,7 @@ interface_edit_static_index :: proc(interface:^interface_state, def: ^engine_ent
{ {
curr_index := &interface.curr_static_index curr_index := &interface.curr_static_index
entity := interface.entities[interface.selected_entity^] entity := interface.entities[interface.selected_entity]
level := interface.world level := interface.world
@@ -216,11 +216,11 @@ interface_shape_def_editor :: proc(def: ^engine_entity_def) -> bool
interface_entity :: proc(interface: ^interface_state) -> bool interface_entity :: proc(interface: ^interface_state) -> bool
{ {
entity_selected := (interface.selected_entity^ != -1) && len(interface.entity_defs) > 0 entity_selected := (interface.selected_entity != -1) && len(interface.entity_defs) > 0
if entity_selected if entity_selected
{ {
def := interface.entity_defs[interface.selected_entity^] def := interface.entity_defs[interface.selected_entity]
def_old := def^ def_old := def^
ret := false ret := false
+1 -1
View File
@@ -1,4 +1,4 @@
package ion package edit2d
import "core:fmt" import "core:fmt"
import b2 "vendor:box2d" import b2 "vendor:box2d"
+414
View File
@@ -0,0 +1,414 @@
package edit2d
import "core:strings"
import "core:os"
import "core:bytes"
import "core:path/filepath"
import "core:crypto/hash"
import "core:time"
import b2 "vendor:box2d"
import "core:fmt"
import "core:encoding/cbor"
/*
The engine can provide helper functions so that it'll be easier to write
the level code in the game
A Typical level code
*/
engine_world :: struct {
world_id : b2.WorldId,
//This in engine code?
static_indexes : map[static_index]int `cbor:"-"`,
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
relations_serializeable : map[ static_index][dynamic]static_index_global,
/*
Seems okay to put the joint defs in engine rather than in game because
we don't add more attributes in joints in the game
Can be changed later without requireing refactor
*/
revolute_joint_defs : [dynamic]revolt_joint_def,
distant_joint_defs : [dynamic]distance_joint_def,
joints : [dynamic]b2.JointId `cbor:"-"`,
cam : Camera,
name : string,
}
@(require_results)
time_to_string_hms :: proc(t: time.Time, buf: []u8) -> (res: string) #no_bounds_check {
assert(len(buf) >= time.MIN_HMS_LEN)
h, m, s := time.clock(t)
buf[7] = '0' + u8(s % 10); s /= 10
buf[6] = '0' + u8(s)
buf[5] = '-'
buf[4] = '0' + u8(m % 10); m /= 10
buf[3] = '0' + u8(m)
buf[2] = '-'
buf[1] = '0' + u8(h % 10); h /= 10
buf[0] = '0' + u8(h)
return string(buf[:time.MIN_HMS_LEN])
}
cbor_flags : cbor.Encoder_Flags :
{
.Self_Described_CBOR,
.Deterministic_Int_Size,
.Deterministic_Float_Size,
}
level_load_from_files :: proc(
$G, $L, $E: typeid,
game: ^G,
state: ^engine_state
)
{
files, err := os.read_all_directory_by_path("levels", context.allocator)
for file in files
{
if !os.exists(file.fullpath) do continue
if os.is_dir(file.fullpath) do continue
level_name := strings.clone(filepath.stem(file.fullpath))
game.levels[level_name] = {}
game.curr_level = strings.clone(level_name)
level_init_from_path(L, E, &game.levels[level_name], file.fullpath, state)
}
}
//Returns engine_world by reading from the file
/*
In the user code
level_name = strings.clone(filepath.stem(path))
game.levels[level_name] = {}
level_init_from_path(&game.levels[level_name], path)
*/
//We don't want the user to configure and use cbor everytime so we want to handle it
//
level_init_from_path :: proc(
$T, $E: typeid,
level_data : ^T,
path : string,
state: ^engine_state,
) -> (ret : bool)
{
level := cast(^engine_world)level_data
if os.is_file(path)
{
level_name := strings.clone(filepath.stem(path))
data, err := os.read_entire_file_from_path(path, context.allocator)
level.name = level_name
//?
level.relations = {}
level.relations = make(map[^static_index][dynamic]static_index_global)
if len(data) > 0
{
unmarshall_err := cbor.unmarshal(data, level_data)
delete(data)
if unmarshall_err == nil
{
level_reload(T, E, level_data)
state.draw.cam = level.cam
for key, val in level.relations_serializeable
{
index := level.static_indexes[key]
entity := level_data.entities[index]
level_data.relations[entity.index] = {}
for v in val do append(&level.relations[entity.index], v)
}
ret = true
}else
{
ret = false
fmt.eprintf("Faield to unmarshall file %s \n", unmarshall_err)
}
}else
{
level_reload(T, E, level_data)
state.draw.cam = level.cam
for key, val in level.relations_serializeable
{
index := level.static_indexes[key]
entity := level_data.entities[index]
level_data.relations[entity.index] = {}
for v in val do append(&level.relations[entity.index], v)
}
//Handle no data
fmt.println("Empty level")
ret = false
}
}else
{
fmt.eprintln("File not exist or is a directory")
ret = false
}
return
}
/*
We can't create the entities
That should be done by the user
*/
level_reload :: proc($T: typeid, $E: typeid, level: ^T)
{
clear(&level.entities)
clear(&level.static_indexes)
if level.world_id != b2.nullWorldId do b2.DestroyWorld(level.world_id)
//initilize world
{
world_def := b2.DefaultWorldDef()
b2.SetLengthUnitsPerMeter(100)
//Get the gravity from config
world_def.gravity = {0, -9.8 * 100}
level.world_id = b2.CreateWorld(world_def)
}
for &def, i in &level.entity_defs
{
//new_entity := engine_create_entity(&def, level.world_id, i32(i))
new_entity : E
new_entity.type = def.type
new_entity.engine = engine_create_entity(&def, level.world_id, i32(i))
//new_entity := entity_create_by_type(&def, level.world_id, i32(i))
append(&level.entities, new_entity)
if new_entity.index != nil do level.static_indexes[new_entity.index^] = i
//if new_entity.type == .PLAYER do level.player_index = i32(i)
}
{
for &def in &level.distant_joint_defs
{
//Get entity_a and entity_b
if def.entity_a <= 0 do continue
if def.entity_b <= 0 do continue
entity_a := &level.entities[level.static_indexes[def.entity_a]]
def.bodyIdA = entity_a.body_id
entity_b := &level.entities[level.static_indexes[def.entity_b]]
def.bodyIdB = entity_b.body_id
append(&level.joints, b2.CreateDistanceJoint(level.world_id, def.def))
}
for &def in &level.revolute_joint_defs
{
//Get entity_a and entity_b
if def.entity_a <= 0 do continue
if def.entity_b <= 0 do continue
entity_a := &level.entities[level.static_indexes[def.entity_a]]
def.bodyIdA = entity_a.body_id
entity_b := &level.entities[level.static_indexes[def.entity_b]]
def.bodyIdB = entity_b.body_id
append(&level.joints, b2.CreateRevoluteJoint(level.world_id, def.def))
}
}
//level.player.e = &level.entities[level.player_index]
}
/*
Saves the level to the levels directory using level's name as filename
It uses core library's hash and cbor to searialize the level into bytes
It assumes that it uses engine_world struct as it's first member
Checks hash of current level state and file's hash
If both hash is same the exits
*/
level_save_to_file :: proc(level_data: ^$T, state: ^engine_state)
{
level := cast(^engine_world)level_data
level_path := fmt.tprintf("levels/%s.cbor", level.name)
level.cam = state.draw.cam
fmt.println(level_path)
//Relation serializeable
{
clear(&level.relations_serializeable)
for key, val in &level.relations
{
if key != nil
{
level.relations_serializeable[key^] = {}
for v in val do append(&level.relations_serializeable[key^], v)
}
}
}
binary, cbor_err := cbor.marshal_into_bytes(level_data^, cbor_flags)
if cbor_err == nil
{
}else
{
fmt.eprintf("Failed to marshall level %s \n", cbor_err)
return
}
//Get hash of current loaded game and current saved game
binary_hash := hash.hash_bytes(.SHA3_512, binary)
fmt.println(binary_hash)
file_hash, hash_err := hash.hash_file_by_name(.SHA3_512, level_path)
//If the hash is same that means no changes has been made
if bytes.compare(binary_hash, file_hash) != 0
{
if !os.exists("levels")
{
err := os.mkdir("levels")
if err != nil
{
fmt.println("Failed to create levels directory")
fmt.println(err)
}
}
/*
Create backeup before saving to file
We create backup by copying the current level file to the backup directory
Backup directory will contain the date and time of the backup
*/
if os.exists(level_path)
{
//Get time and date to create file and folder name for backup
curr_time := time.now()
buf : [time.MIN_YYYY_DATE_LEN]u8
time_buf : [time.MIN_HMS_LEN]u8
date_str := time.to_string_yyyy_mm_dd(curr_time, buf[:])
time_str := time_to_string_hms(curr_time, time_buf[:])
backup_dir_path := fmt.tprintf("levels/backups/%s",date_str)
backup_file_path := fmt.tprintf("levels/backups/%s/%s-%s", date_str, level.name, time_str)
bk_err := os.make_directory_all(backup_dir_path)
if bk_err != nil
{
fmt.eprintf("Failed to create backup directory %s \n", bk_err)
}
else{
bk_err := os.copy_file(backup_file_path, level_path)
if bk_err != nil
{
fmt.eprintf("Failed to copy file %s\n", bk_err)
}
}
}
//Save the current binary to file
save_err := os.write_entire_file_from_bytes(level_path, binary)
if save_err != nil
{
fmt.eprintf("Failed to save level %s\n", save_err)
}
}else{
fmt.println("No changes detected")
}
}
/*
The engine assumes that it has attribute entities
*/
game_to_engine_entities :: proc($Game: typeid, game_data: ^Game, state: ^engine_state)
{
level := &game_data.levels[game_data.curr_level]
interface : ^interface_state = &game_data.interface
clear(&interface.entities)
clear(&interface.entity_defs)
for &entity in &level.entities
{
append(&interface.entities, &entity.engine)
}
for &def in &level.entity_defs
{
append(&interface.entity_defs, &def.engine)
}
interface.state = state
interface.world = &level.engine
}
+1 -1
View File
@@ -1,4 +1,4 @@
package ion package edit2d
import "core:slice" import "core:slice"
import b2 "vendor:box2d" import b2 "vendor:box2d"