def tick args
defaults args
input args
calc args
render args
end
def defaults args
# uncomment the line below to slow the game down by a factor of 4 -> 15 fps (for debugging)
# args.gtk.slowmo! 4
args.state.player ||= {
x: 144, # render x of the player
y: 32, # render y of the player
w: 144 * 2, # render width of the player
h: 72 * 2, # render height of the player
dx: 0, # velocity x of the player
action: :standing, # current action/status of the player
action_at: 0, # frame that the action occurred
previous_direction: 1, # direction the player was facing last frame
direction: 1, # direction the player is facing this frame
launch_speed: 4, # speed the player moves when they start running
run_acceleration: 1, # how much the player accelerates when running
run_top_speed: 8, # the top speed the player can run
friction: 0.9, # how much the player slows down when have stopped attempting to run
anchor_x: 0.5, # render anchor x of the player
anchor_y: 0 # render anchor y of the player
}
end
def input args
# if the directional has been pressed on the input device
if args.inputs.left_right != 0
# determine if the player is currently running or not,
# if they aren't, set their dx to their launch speed
# otherwise, add the run acceleration to their dx
if args.state.player.action != :running
args.state.player.dx = args.state.player.launch_speed * args.inputs.left_right.sign
else
args.state.player.dx += args.inputs.left_right * args.state.player.run_acceleration
end
# capture the direction the player is facing and the previous direction
args.state.player.previous_direction = args.state.player.direction
args.state.player.direction = args.inputs.left_right.sign
end
end
def calc args
# clamp the player's dx to the top speed
args.state.player.dx = args.state.player.dx.clamp(-args.state.player.run_top_speed, args.state.player.run_top_speed)
# move the player by their dx
args.state.player.x += args.state.player.dx
# capture the player's hitbox
player_hitbox = hitbox args.state.player
# check boundary collisions and stop the player if they are colliding with the ednges of the screen
if (player_hitbox.x - player_hitbox.w / 2) < 0
args.state.player.x = player_hitbox.w / 2
args.state.player.dx = 0
# if the player is not standing, set them to standing and capture the frame
if args.state.player.action != :standing
args.state.player.action = :standing
args.state.player.action_at = args.state.tick_count
end
elsif (player_hitbox.x + player_hitbox.w / 2) > 1280
args.state.player.x = 1280 - player_hitbox.w / 2
args.state.player.dx = 0
# if the player is not standing, set them to standing and capture the frame
if args.state.player.action != :standing
args.state.player.action = :standing
args.state.player.action_at = args.state.tick_count
end
end
# if the player's dx is not 0, they are running. update their action and capture the frame if needed
if args.state.player.dx.abs > 0
if args.state.player.action != :running || args.state.player.direction != args.state.player.previous_direction
args.state.player.action = :running
args.state.player.action_at = args.state.tick_count
end
elsif args.inputs.left_right == 0
# if the player's dx is 0 and they are not currently trying to run (left_right == 0), set them to standing and capture the frame
if args.state.player.action != :standing
args.state.player.action = :standing
args.state.player.action_at = args.state.tick_count
end
end
# if the player is not trying to run (left_right == 0), slow them down by the friction amount
if args.inputs.left_right == 0
args.state.player.dx *= args.state.player.friction
# if the player's dx is less than 1, set it to 0
if args.state.player.dx.abs < 1
args.state.player.dx = 0
end
end
end
def render args
# determine if the player should be flipped horizontally
flip_horizontally = args.state.player.direction == -1
# determine the path to the sprite to render, the idle sprite is used if action == :standing
path = "sprites/link-idle.png"
# if the player is running, determine the frame to render
if args.state.player.action == :running
# the sprite animation's first 3 frames represent the launch of the run, so we skip them on the animation loop
# by setting the repeat_index to 3 (the 4th frame)
frame_index = args.state.player.action_at.frame_index(count: 9, hold_for: 8, repeat: true, repeat_index: 3)
path = "sprites/link-run-#{frame_index}.png"
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 230, text: "action: #{args.state.player.action}" }
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 200, text: "action_at: #{args.state.player.action_at}" }
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 170, text: "frame_index: #{frame_index}" }
else
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 230, text: "action: #{args.state.player.action}" }
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 200, text: "action_at: #{args.state.player.action_at}" }
args.outputs.labels << { x: args.state.player.x - 144, y: args.state.player.y + 170, text: "frame_index: n/a" }
end
# render the player's hitbox and sprite (the hitbox is used to determine boundary collision)
args.outputs.borders << hitbox(args.state.player)
args.outputs.borders << args.state.player
# render the player's sprite
args.outputs.sprites << args.state.player.merge(path: path, flip_horizontally: flip_horizontally)
end
def hitbox entity
{
x: entity.x,
y: entity.y + 5,
w: 64,
h: 96,
anchor_x: 0.5,
anchor_y: 0
}
end
$gtk.reset