Skip to content

Instantly share code, notes, and snippets.

@Srekel
Created July 27, 2018 10:02
Show Gist options
  • Select an option

  • Save Srekel/1a3788f9651946ec29ae4ea95040379a to your computer and use it in GitHub Desktop.

Select an option

Save Srekel/1a3788f9651946ec29ae4ea95040379a to your computer and use it in GitHub Desktop.
The jumping state for characters in The Showdown Effect
-- This is the jumping state for characters in The Showdown Effect (Paradox/Arrowhead/Pixeldiet).
-- https://store.steampowered.com/app/204080/The_Showdown_Effect/
-- Uploaded to show how complex player control code can be.
-- In addition to this file there are 34 other states the player character can be in. Thirty four!
-- Judging by file size the jumping state is the third most complicated.
--
-- The purpose of these states is to read the player's input, check the character's current state, and evaluate
-- what should happen next (either do something specific to the state or switch to another state)
--
-- There may be bugs here - this is potentially not the very last version shipped in the game, and the
-- game certainly had bugs, though it's character control was nifty :)
-- Note that none of the "physics" is done in the state, that's done in a completely different system.
--
-- You can direct questions to @srekel :)
local EntityAux_set_input = EntityAux.set_input
local EntityAux_state = EntityAux.state
local math_abs = math.abs
local math_sign = math.sign
local Quaternion_forward = Quaternion.forward
local Unit_animation_event = Unit.animation_event
local Unit_local_rotation = Unit.local_rotation
local Vector3_unbox = Vector3.unbox
CharacterStateJumping = class(CharacterStateJumping, CharacterStateBase)
function CharacterStateJumping:init(context)
CharacterStateBase.init(self, context, "jumping")
end
function CharacterStateJumping:on_enter(context)
local unit = self._unit
local state = context.state
local input = context.input
local input_data = input.input_data
local network_transport = context.network_transport
local char_state_input = context.char_state_input
local movement_settings = MovementSettings.get_movement_settings(unit)
StateContext.event():trigger("character_voice_event", unit, "jump")
if char_state_input.from_sliding then
network_transport:send_animation_event(unit, "slide_jump")
Unit.animation_event(unit, "slide_jump")
elseif char_state_input.from_rolling then
network_transport:send_animation_event(unit, "jump")
Unit.animation_event(unit, "jump")
elseif char_state_input.from_walking then
network_transport:send_animation_event(unit, "jump_forward")
Unit.animation_event(unit, "jump_forward")
else
network_transport:send_animation_event(unit, "jump")
Unit.animation_event(unit, "jump")
end
EntityAux.state(unit, "locomotion").touch_ground = false
EntityAux_set_input(unit, "locomotion", "force_in_air", true)
EntityAux_set_input(unit, "locomotion", "max_velocity", nil)
EntityAux_set_input(unit, "locomotion", "max_velocity", movement_settings.large_velocity)
-- Flow event for playing effects
Unit.flow_event(unit, "jump")
network_transport:send_flow_event(unit, "jump")
-- Hold down jump and you jump farther, simply tap it for a microsecond and you do a tiny skip
self.wanted_velocity = char_state_input.wanted_velocity
if math.abs(input_data.move.x) < 0.2 and self.wanted_velocity then
self.wanted_velocity = table.clone(self.wanted_velocity)
self.wanted_velocity[1] = 0
end
self.max_jump_time = 1
self.jump_timer = 0.0
context.state.can_block = true
context.state.can_enter_elevator = false
if char_state_input.forced_direction then
EntityAux.set_input(unit, "locomotion", "auto_rotate", false)
else
EntityAux.set_input(unit, "locomotion", "auto_rotate", "air")
end
if input.aiming then
Unit.animation_event(unit, "aim")
context.network_transport:send_animation_event(unit, "aim")
EntityAux.set_input(unit, "locomotion", "auto_rotate", "aim")
elseif input.blocking then
EntityAux.set_input(unit, "locomotion", "auto_rotate", false)
else
local direction = char_state_input.forced_direction or math.sign(input_data.move.x)
char_state_input.forced_direction = nil
if direction == 0 then
direction = state.direction
end
if direction == 1 and state.direction ~= 1 then
EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil)
EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_left)
elseif direction == -1 and state.direction ~= -1 then
EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil)
EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_right)
end
end
if self.wanted_velocity then
EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", nil)
EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", { wanted_velocity=self.wanted_velocity, time=0.05 })
self.up_acceleration = 100
self.up_acc = {0,0,self.up_acceleration}
else
self.up_acceleration = nil
end
self.jump_boost_timer = 0
self.from_ropeclimbing = context.char_state_input.from_ropeclimbing
if context.char_state_input.from_ladderclimbing then
self.ladderclimbing_timer = 0.5
else
self.ladderclimbing_timer = nil
end
self.climb_unit = state.climb_focus
self.enable_wallslide = not context.char_state_input.disable_wallslide
self.wallslide_timer = movement_settings.jump_wallslide_timer
self.air_control_acceleration = {0,0,0}
self.from_sprinting = context.char_state_input.from_sprinting
self.matrix_jump = context.char_state_input.from_sliding
self.has_been_moved = false
self.ignore_air_control = context.char_state_input.ignore_air_control == true
self.ledge_climb_buffered = false
state.last_z_ground_pos = UnitCache_get_position(unit).z
self.acceleration_set = false
self.going_upwards = true
end
function CharacterStateJumping:on_exit(context)
local unit = self._unit
context.state.can_block = false
context.state.can_enter_elevator = true
EntityAux.append_input(unit, "locomotion", "remove_timed_acceleration", "character_state_jumping")
EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", nil)
-- EntityAux.set_input(unit, "locomotion", "clear_max", true)
end
function CharacterStateJumping:update(context)
local dt = context.dt
local input = context.input
local input_data = input.input_data
local internal = context.internal
local state = context.state
local em = context.entity_manager
local unit = context.unit
local csm = self._csm
local movement_settings = MovementSettings.get_movement_settings(unit)
local loco_ext = internal.loco_ext
local velocity = Vector3.unbox(loco_ext.state.velocity)
self.jump_timer = self.jump_timer + dt
self.jump_boost_timer = self.jump_boost_timer + dt
if self.up_acceleration ~= nil and input.held_time.jump ~= 0 and self.jump_timer <= 0.2 and not self.acceleration_set then
self.up_acc[3] = self.up_acceleration
EntityAux.set_input(unit, "locomotion", "force_in_air", true)
EntityAux.append_input(unit, "locomotion", "timed_acceleration", { acceleration=self.up_acc, id="character_state_jumping", time=0.2 })
self.acceleration_set = true
self.going_upwards = true
elseif self.jump_timer <= 0.2 and input_data.jump < 0.1 then
EntityAux.append_input(unit, "locomotion", "remove_timed_acceleration", "character_state_jumping")
self.going_upwards = false
elseif self.jump_timer > 0.2 then
self.going_upwards = false
end
if input_data.move.y > 0 and not input.attacking then
local found = nil
local d_swing = state.swing_colliders.up
for k,v in pairs(d_swing) do
found = v
break
end
if found then
context.swing_info = found
csm:change_state(context, "swinging")
return
end
end
local can_not_air_control = self.ignore_air_control or math.sign(input_data.move.x) == math.sign(velocity.x) and math.abs(velocity.x) > 7
if not can_not_air_control then -- eew
Vector3.box(self.air_control_acceleration, 40 * Vector3(math.sign(input_data.move.x), 0, 0))
EntityAux.append_input(unit, "locomotion", "acceleration", self.air_control_acceleration)
end
if loco_ext.state.touch_left then
if input.justpressed.jump then
EntityAux.state(unit, "locomotion").in_air = true
EntityAux.state(unit, "locomotion").touch_left = false
local jump_velocity = movement_settings.walljump_velocity
jump_velocity[1] = math.abs(jump_velocity[1]) * 1
EntityAux.set_input(unit, "locomotion", "wanted_velocity", nil)
EntityAux.set_input(unit, "locomotion", "wanted_velocity", jump_velocity)
context.char_state_input.forced_direction = 1
context.char_state_input.ignore_air_control = true
end
elseif loco_ext.state.touch_right then
if input.justpressed.jump then
EntityAux.state(unit, "locomotion").in_air = true
EntityAux.state(unit, "locomotion").touch_right = false
local jump_velocity = movement_settings.walljump_velocity
jump_velocity[1] = math.abs(jump_velocity[1]) * -1
EntityAux.set_input(unit, "locomotion", "wanted_velocity", nil)
EntityAux.set_input(unit, "locomotion", "wanted_velocity", jump_velocity)
context.char_state_input.forced_direction = -1
context.char_state_input.ignore_air_control = true
end
end
local char_state_input = context.char_state_input
if char_state_input.forced_direction then
EntityAux.set_input(unit, "locomotion", "auto_rotate", false)
local direction = char_state_input.forced_direction or math.sign(input_data.move.x)
char_state_input.forced_direction = nil
if direction == 0 then
direction = state.direction
end
if direction == 1 and state.direction ~= 1 then
EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil)
EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_left)
elseif direction == -1 and state.direction ~= -1 then
EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil)
EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_right)
end
end
if input.justpressed.jump then
self.ledge_climb_buffered = true
end
if loco_ext.state.touch_ground and self.jump_timer > 0.2 then
if math.abs(input_data.move.x) ~= 0 then
csm:change_state(context, "walking")
elseif loco_ext.state.roof_available then
csm:change_state(context, "crouching")
else
csm:change_state(context, "standing")
end
return
end
if state.rope_colliding_grab and input_data.move.y ~= 0 then
if self.from_ropeclimbing then
if self.climb_unit ~= state.climb_focus then
csm:change_state(context, "ropeclimbing")
return
end
else
csm:change_state(context, "ropeclimbing")
return
end
end
if self.ladderclimbing_timer and self.ladderclimbing_timer > 0.0 then
self.ladderclimbing_timer = self.ladderclimbing_timer - dt
elseif state.ladder_colliding_head and state.ladder_colliding_feet and input_data.move.y > 0 and not input.firing_heavy_weapon then
context.char_state_input.from_jumping = true
csm:change_state(context, "ladderclimbing")
return
end
if loco_ext.state.moved_this_frame then
self.has_been_moved = true
end
local ls = loco_ext.state
local ledge_available = ls.ledge_valid and ls.ledge_available and not ls.roof_available
if (input_data.jump > 0.2 or input_data.move.y > 0.2) and ledge_available and self.has_been_moved then
local dir_x = math.sign(Quaternion.forward(Unit.local_rotation(unit, 0)).x)
local left_check = ls.ledge_available_left and input_data.move.x < -0.75
local right_check = ls.ledge_available_right and input_data.move.x > 0.75
if left_check or right_check then
context.ledge_position = ls.ledge_position
context.char_state_input.from_jumping = true
context.char_state_input.ledge_climb_buffered = self.ledge_climb_buffered
csm:change_state(context, "ledgeclimbing")
return
end
end
local velocity = Vector3.unbox(loco_ext.state.velocity)
if velocity.z < 0 and not self.going_upwards then
context.char_state_input.allow_air_control = true
context.char_state_input.matrix_jump = self.matrix_jump
context.char_state_input.from_sprinting = self.from_sprinting
context.char_state_input.ledge_climb_buffered = self.ledge_climb_buffered
csm:change_state(context, "in_air")
return
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment