~slendi/techmates

516d374fea56b52f4dac44224819804fb549117b — Slendi 2 months ago 235751d master
Add animations

Signed-off-by: Slendi <slendi@socopon.com>
4 files changed, 302 insertions(+), 41 deletions(-)

A characters/StickMan/settings.ini
R spritesheet.png => characters/StickMan/spritesheet.png
M main.odin
M settings.ini
A characters/StickMan/settings.ini => characters/StickMan/settings.ini +30 -0
@@ 0,0 1,30 @@
[general]
name = StickMan
spritesheet = spritesheet.png
scale = 4

[common]
w = 32
h = 32
frame_layout = column
fps = 5
frames = 2
grabx = 15
graby = 11
offy = 0

[idle]
offx = 0
frames = 2

[walk]
offx = 32

[grab]
offx = 64

[fall]
offx = 96

[drag]
offx = 128

R spritesheet.png => characters/StickMan/spritesheet.png +0 -0
M main.odin => main.odin +270 -12
@@ 4,6 4,7 @@ import "base:runtime"
import "core:encoding/ini"
import "core:fmt"
import "core:math"
import "core:os"
import path "core:path/filepath"
import "core:strconv"
import "core:strings"


@@ 13,6 14,7 @@ import sdl "vendor:sdl2"
import sdl_img "vendor:sdl2/image"

Vec2 :: [2]int
Vec2f :: [2]f32

FrameLayout :: enum {
	Column,


@@ 24,11 26,210 @@ Anim :: struct {
	fps:             f64,
	frames:          int,
	size, grab, off: Vec2,
	// INTERNAL
	current_frame:   int,
}

CharacterState :: enum {
	IdleLeft,
	IdleRight,
	Falling,
	WalkingLeft,
	WalkingRight,
	Grabbed,
	DraggedLeft,
	DraggedRight,
}

Character :: struct {
	spritesheet, name: string,
	animations:        map[string]Anim,
	scale:             int,
	// INTERNAL
	t:                 f64,
	is_grounded:       bool,
	state:             CharacterState,
}

Character_get_anim_for_state :: proc(ch: ^Character) -> (anim: ^Anim, fliph: bool) {
	switch ch.state {
	case .IdleLeft:
		return &ch.animations["idle"], false
	case .IdleRight:
		return &ch.animations["idle"], true
	case .Falling:
		return &ch.animations["fall"], true
	case .WalkingLeft:
		walk, ok := &ch.animations["walk"]
		if !ok {
			return &ch.animations["walk_left"], false
		}
		return walk, false
	case .WalkingRight:
		walk, ok := &ch.animations["walk"]
		if !ok {
			return &ch.animations["walk_right"], false
		}
		return walk, true
	case .Grabbed:
		return &ch.animations["grab"], true
	case .DraggedLeft:
		drag, ok := &ch.animations["drag"]
		if !ok {
			return &ch.animations["drag_left"], false
		}
		return drag, false
	case .DraggedRight:
		drag, ok := &ch.animations["drag"]
		if !ok {
			return &ch.animations["drag_right"], false
		}
		return drag, true
	}
	return
}

Character_draw :: proc(
	character: ^Character,
	renderer: ^sdl.Renderer,
	tex: ^sdl.Texture,
	anim: ^Anim,
	fliph: bool,
	pos: Vec2f,
) {
	dst := sdl.Rect {
		x = i32(pos.x),
		y = i32(pos.y),
		w = i32(anim.size.x) * i32(character.scale),
		h = i32(anim.size.y) * i32(character.scale),
	}

	src := sdl.Rect {
		x = i32(anim.off.x),
		y = i32(anim.off.y),
		w = i32(anim.size.x),
		h = i32(anim.size.y),
	}

	if anim.frame_layout == .Column {
		src.y += i32(anim.size.y * anim.current_frame)
	} else {
		src.x += i32(anim.size.x * anim.current_frame)
	}

	sdl.RenderCopyEx(renderer, tex, &src, &dst, 0, nil, .HORIZONTAL if fliph else .NONE)
}

Character_update :: proc(ch: ^Character, dt: f64) {
	if ch.is_grounded {
		ch.state = .IdleLeft
	} else {
		ch.state = .Falling
	}

	ch.t += dt
	anim, _ := Character_get_anim_for_state(ch)

	if ch.t > 1 / anim.fps {
		ch.t -= 1 / anim.fps
		anim.current_frame += 1
		anim.current_frame %= anim.frames
	}
}

AppState :: struct {
	tex:      []^sdl.Texture,
	surf:     []^sdl.Surface,
	chs:      []Character,
	chs_path: []string,
}

AppState_save :: proc(state: ^AppState, out: string) -> (err: os.Error) {
	m := ini.Map{}
	defer delete(m)

	loaded := map[string]string{}
	defer delete(loaded)

	for &ch in state.chs_path {
		loaded[ch] = "true"
	}
	m["loaded"] = loaded

	data := ini.save_map_to_string(m, context.allocator)
	defer delete(data)
	os.write_entire_file_or_err(out, transmute([]u8)data) or_return

	return
}

AppState_create_from_file :: proc(
	renderer: ^sdl.Renderer,
	cfg: string = "settings.ini",
) -> (
	state: AppState,
	ok: bool,
) {
	ini, ini_err, ok_ini := ini.load_map_from_path(cfg, context.allocator, {key_lower_case = true})
	if !ok_ini {
		os.write_entire_file(cfg, transmute([]u8)string("[loaded]\r\nStickMan=true"))
		return AppState_create_from_file(renderer, cfg)
	}

	loaded, ok2 := ini["loaded"]
	if !ok2 {
		fmt.printf("ERROR: Failed to find loaded section in {}\n", cfg)
		return
	}

	if len(loaded) == 0 {
		fmt.printf("WARNING: No character found. Loading first one that can be found.")
		chs := get_available_characters()
		if len(chs) < 1 {
			fmt.printf("ERROR: No characters found.")
			return
		}
		loaded[chs[0]] = "true"
	}

	tex := [dynamic]^sdl.Texture{}
	surf := [dynamic]^sdl.Surface{}
	chs_path := [dynamic]string{}
	characters := [dynamic]Character{}
	for ch_path, v in loaded {
		if v != "true" do continue

		character_dir := path.join({"characters", ch_path})
		defer delete(character_dir)
		character, ok := Character_load(character_dir)
		if !ok {
			fmt.println("WARNING: Failed to load character `{}'", ch_path)
			continue
		}

		// FIXME: Load spritesheet
		spritesheet_path := path.join({character_dir, character.spritesheet})
		defer delete(spritesheet_path)

		spr_sheet_path := strings.clone_to_cstring(spritesheet_path)
		defer delete(spr_sheet_path)

		surface := sdl_img.Load(spr_sheet_path)
		texture := sdl.CreateTextureFromSurface(renderer, surface)

		append(&tex, texture)
		append(&surf, surface)
		append(&chs_path, ch_path)
		append(&characters, character)
	}

	state.tex = tex[:]
	state.surf = surf[:]
	state.chs_path = chs_path[:]
	state.chs = characters[:]
	ok = true

	return
}

ini_value_or_common :: proc(m: ^ini.Map, section, key: string) -> (string, bool) {


@@ 64,10 265,15 @@ Character_load :: proc(dir: string = ".") -> (ch: Character, ok: bool) {
		return
	}

	fmt.println(ini)

	ch.name = ini["general"]["name"]
	ch.spritesheet = ini["general"]["spritesheet"]
	ch.scale, ok = strconv.parse_int(ini["general"]["scale"] or_else "1")
	fmt.println("scale", ch.scale)
	if !ok {
		return
	}
	//ch.scale = 1
	ok = false

	required_animations := []string{"idle", "walk", "fall"}
	for &anim in required_animations {


@@ 214,14 420,50 @@ Character_load :: proc(dir: string = ".") -> (ch: Character, ok: bool) {
	return
}

main :: proc() {
	ch, ok := Character_load()
	if !ok {
		panic("Invalid character")
get_available_characters :: proc(app_dir: string = ".") -> []string {
	characters_dir := path.join({app_dir, "characters"})
	defer delete(characters_dir)

	chs := [dynamic]string{}

	dir, err := os.open(characters_dir)
	if err != nil {
		fmt.println("WARNING: No characters directory found")
		return {}
	}

	if !os.is_dir(characters_dir) {
		fmt.println("ERROR: Characters directory is not a directory")
		return {}
	}

	fis, err2 := os.read_dir(dir, 0)
	if err2 != nil {
		fmt.println("ERROR: Failed to read files in characters directory")
		return {}
	}

	fmt.printf("%#v\n", ch)
	for &fi in fis {
		if !fi.is_dir do continue

		settings_path := path.join({fi.fullpath, "settings.ini"})
		defer delete(settings_path)

		fi_settings, err := os.stat(settings_path)
		defer os.file_info_delete(fi_settings)

		if err != nil {
			fmt.println("WARNING: Could not stat() `settings.ini' for `{}'", fi.name)
			continue
		}

		append(&chs, fi.name)
	}

	return chs[:]
}

main :: proc() {
	desktop_width := win.GetSystemMetrics(win.SM_CXSCREEN)
	desktop_height := win.GetSystemMetrics(win.SM_CYSCREEN)



@@ 242,6 484,14 @@ main :: proc() {
	if renderer == nil do panic("Failed to create renderer")
	defer sdl.DestroyRenderer(renderer)

	app_state, ok := AppState_create_from_file(renderer)
	if !ok {
		fmt.println("Failed to read AppState")
		return
	}

	fmt.printf("%#v", app_state)

	event := sdl.Event{}
	sdl.StartTextInput()



@@ 274,11 524,19 @@ main :: proc() {
		sdl.SetRenderDrawColor(renderer, 255, 0, 255, 255)
		sdl.RenderClear(renderer)

		rect.x = i32(math.sin_f64(t / 100) * 100) + 200
		rect.y = i32(math.cos_f64(t / 100) * 100) + 200

		sdl.SetRenderDrawColor(renderer, 255, 0, 0, 255)
		sdl.RenderFillRect(renderer, &rect)
		rect.x = i32(math.sin_f64(t / 1000) * 100) + 200
		rect.y = i32(math.cos_f64(t / 1000) * 100) + 200

		for &ch, i in app_state.chs {
			Character_update(&ch, dt / 1000)
			Character_draw(
				&ch,
				renderer,
				app_state.tex[i],
				Character_get_anim_for_state(&ch),
				Vec2f{f32(rect.x), f32(rect.y)},
			)
		}

		sdl.RenderPresent(renderer)


M settings.ini => settings.ini +2 -29
@@ 1,29 1,2 @@
[general]
name = StickMan
spritesheet = spritesheet.png

[common]
w = 32
h = 32
frame_layout = column
fps = 5
frames = 2
grabx = 15
graby = 11
offy = 0

[idle]
offx = 0
frames = 2

[walk]
offx = 32

[grab]
offx = 64

[fall]
offx = 96

[drag]
offx = 128
[loaded]
StickMan=true
\ No newline at end of file