package rog import "core:fmt" import rl "vendor:raylib" import "core:math" import "core:math/linalg" key :: rl.KeyboardKey mouse :: rl.MouseButton // fake coordinate system. basically, I don't want to depend on screen size for coordinates. // for convenience, the sizes will be floats, but tile based. (aka unit) // Pos will be in units. // // about display, it can depend. at least UNITS_ON_SCREEN x UNITS_ON_SCREEN UNITS_ON_SCREEN :: 16 // what should the Pos be? top-left corner of the entity? bottom-middle? // let's make it bottom middle for now. main :: proc() { rl.SetConfigFlags({ rl.ConfigFlag.VSYNC_HINT, rl.ConfigFlag.WINDOW_RESIZABLE }) rl.InitWindow(800, 600, "mein rog") defer rl.CloseWindow() screen_width := f32(rl.GetScreenWidth()) screen_height := f32(rl.GetScreenHeight()) player_tex := rl.LoadTexture("sprite/you8x8.png") defer rl.UnloadTexture(player_tex) bat_tex := rl.LoadTexture("sprite/weapon_test.png") defer rl.UnloadTexture(bat_tex) player: Player player.pos = { 1, 1 } // TEMP Attack :: struct { t: f32, dir: [2]f32} attack: Maybe(Attack) = nil rl.SetTargetFPS(60) rot: f32 for !rl.WindowShouldClose() { dir := held_direction() defer rot += 1 if player.state == nil && dir != {0, 0} && rl.IsKeyPressed(key.SPACE) { player.state = Dash{ dir = dir } } switch &s in player.state { case nil: { old_player_pos := player.pos player.pos += move_player(dir) player_moved := old_player_pos != player.pos update_walk_animation(&player.walk_anim, player_moved) } case Dash: { dash_time :: .1 dash_speed_units_per_sec :: 25 // debug tims // dash_time :: 2 // dash_speed_units_per_sec :: 2 s.dash_time += rl.GetFrameTime() / dash_time player.pos += s.dir * dash_speed_units_per_sec * rl.GetFrameTime() * math.cos(s.dash_time) // 0..1, not PI'd on purpose. when t = 1, movement is slowed down but not entirely. if s.dash_time >= 1 { player.state = nil player.walk_anim.t = 0 } } } min_screen_size: f32 = math.min(f32(rl.GetScreenWidth()), f32(rl.GetScreenHeight())) unit: Unit = { min_screen_size / UNITS_ON_SCREEN } mouse_screen := rl.GetMousePosition() mouse_units := screen2pos(mouse_screen, unit) mouse_rel := player.pos - mouse_units mouse_norm := linalg.normalize0(mouse_rel) switch &a in attack { case nil: {} case Attack: { a.t += rl.GetFrameTime() * max(math.cos(3 * a.t * math.PI), .5) * 10 if (a.t >= 1) { attack = nil } } } if rl.IsMouseButtonPressed(mouse.LEFT) { attack = Attack{ t = 0, dir = mouse_norm } } // TODO: it looks bad. I should just scale textures and stuff up and translate it to the screen bruh. // also, we should draw the edges too. rl.BeginDrawing() { player_width :: 1. rl.ClearBackground(rl.RAYWHITE) switch s in player.state { case nil: { draw_shadow(player.pos, player_width, unit, player.walk_anim) draw_scaled_texture(player_tex, animate_walk(pos2screen(player.pos, unit), player.walk_anim, unit), player_width, unit, flipped=mouse_rel.x >= 0) } case Dash: { draw_shadow(player.pos, player_width, unit) hscale := 1 - math.cos(s.dash_time) * .5 draw_scaled_texture(player_tex, pos2screen(player.pos, unit), player_width, unit, flipped=mouse_rel.x >= 0, hscale=hscale) sdash := fmt.caprintf("%f", s.dash_time) defer delete(sdash) rl.DrawText(sdash, 0, 50, 50, rl.RED) } } weapon_thing := AtYOffset{rotation = 0, off = { 0, .35 }, extension = .3 } { bat_width :: .5 if mouse_norm == { 0, 0 } { mouse_norm = -dir } attk, ok := attack.? if !ok { bat_pos := player.pos + mouse_norm * .8 bat_pos.y -= .35 draw_scaled_texture(bat_tex, pos2screen(bat_pos, unit), bat_width, unit, hscale=1.5) } else { base_rot := (math.atan2(attk.dir.x, -attk.dir.y) * 180 / math.PI) + 90 attk_t := attk.t if attk.dir.x > 0 { base_rot += 180 attk_t *= -1 } bat_pos := player.pos rot := base_rot + attk_t * 180 // bat_pos.y -= .35 draw_scaled_texture(bat_tex, pos2screen(bat_pos, unit), bat_width, unit, hscale=1.5, rotate=AtYOffset{rotation = rot, off = { 0, .35 },extension = .3 }) } } if false { bat_width :: .5 pos := player.pos // pos += { 0, 1 } origin: [2]f32 = {0, 0} // {scaled(1, unit), scaled(1, unit)} draw_scaled_texture(bat_tex, pos2screen(pos, unit), bat_width, unit, hscale=1.5, rotate=weapon_thing) rl.DrawCircleV(pos2screen(pos, unit), 5, rl.RED) } spos := fmt.caprintf("%f, %f", mouse_rel.x, mouse_rel.y) defer delete(spos) rl.DrawText(spos, 0, 0, 50, rl.RED) satt := fmt.caprintf("%v", attack) defer delete(satt) rl.DrawText(satt, 0, 100, 50, rl.RED) } rl.EndDrawing() } } // TODO: coordinate change? incorrectly draws textures but thats the result of drawing from top left. AtYOffset :: struct { rotation: f32, off: [2]f32, extension: f32 } draw_scaled_texture :: proc(tex: rl.Texture2D, screen_pos: Pos, size: f32, unit: Unit, flipped: bool = false, hscale: Maybe(f32) = nil, rotate: union {AtYOffset} = nil) { pos := screen_pos expected_size := size * unit.unit_size scale := expected_size / f32(tex.width) rect := rl.Rectangle{0, 0, f32(tex.width) * ((flipped) ? -1 : 1), f32(tex.height)} scaled_size := [2]f32{ f32(tex.width), f32(tex.height) } * scale hscaled_size := scaled_size.y * (hscale.? or_else 1) origin: [2]f32 = { scaled_size.x / 2, hscaled_size } rotation: f32 = 0 switch r in rotate { case nil: {} // here, origin is from the bottom-middle case AtYOffset: { pos.x -= scaled(r.off.x, unit) pos.y -= scaled(r.off.y, unit) origin.y += scaled(r.extension, unit) rotation = r.rotation } } // rl.DrawTexturePro(tex, rect, {screen_pos.x - scaled_size.x / 2, screen_pos.y - hscaled_size, scaled_size.x, hscaled_size}, origin, rotation, rl.WHITE) rl.DrawTexturePro(tex, rect, {pos.x, pos.y, scaled_size.x, hscaled_size}, origin, rotation, rl.WHITE) } draw_shadow :: proc(pos: Pos, entity_width: f32, unit: Unit, walk_anim: Maybe(WalkAnim) = nil) { width := entity_width / 3 height := entity_width / 6 anim, ok := walk_anim.? if ok { // copied from animate_walk. I wonder if we can somehow make the formulate "codependent"? height -= math.sin(anim.t) * .08 width -= math.sin(anim.t) * .08 } // todo: make shadow partially transparent rl.DrawEllipseV(pos2screen({ pos.x, pos.y }, unit), scaled(width, unit), scaled(height, unit), rl.BLACK) } Pos :: [2]f32 Player :: struct { pos: Pos, state: union { Dash, }, walk_anim: WalkAnim, } // states Dash :: struct { dir: Pos, dash_time: f32, // 0-1 values bruv. } WalkAnim :: struct { t: f32, } Unit :: struct { unit_size: f32, } scaled :: proc(size: f32, unit: Unit) -> f32 { return unit.unit_size * size } pos2screen :: proc(pos: Pos, unit: Unit) -> Pos { return pos * unit.unit_size } screen2pos :: proc(pos: Pos, unit: Unit) -> Pos { return pos / unit.unit_size } animate_walk :: proc(pos: Pos, walk_anim: WalkAnim, unit: Unit) -> Pos { return { pos.x, pos.y - math.sin(walk_anim.t) * scaled(.2, unit) } } update_walk_animation :: proc(walk_anim: ^WalkAnim, player_moved: bool) { if player_moved || walk_anim.t != 0 { change: f32 = 30 if !player_moved { change = change * 2 } walk_anim.t += rl.GetFrameTime() * change if walk_anim.t >= math.PI { walk_anim.t = 0 } } } held_direction :: proc () -> [2]f32 { change: [2]f32 if rl.IsKeyDown(key.W) { change.y -= 1 } if rl.IsKeyDown(key.S) { change.y += 1 } if rl.IsKeyDown(key.A) { change.x -= 1 } if rl.IsKeyDown(key.D) { change.x += 1 } return linalg.normalize0(change) } move_player :: proc (norm_dir: [2]f32) -> [2]f32 { units_per_sec :: 7 return norm_dir * units_per_sec * rl.GetFrameTime() }