1) How character moves
2) How character jumps
3) How sprite changes during moving
----------------------------------------------ONE------------------------------------------------------
First and second points are connected to each other. That's because I use finite state machines. Make long story short, object changes it's state depending on what button you press. At the very beginning player object(later just player) is in move state. So if you press any move button, player moves, but if press space button, player's state changes to jump state, player jump and after that returns to move state.
Well, let me show you how it looks in code.
We have enumeration called player and variable called state_ which contains player's state
enum player {
//Player states
move,
jump
}
starting_state_ = player.move;
state_ = starting_state_;
There are two states: move and jump. Code of our states is located in different user events.
Depends on in what state we are now, we run user event we need.
Running user events is in step event.
Step event:
The "main state" is move state, because from this state we go to other states. If we press space button we go to jump state. After, jumping state automatically returns to move state.
That's all for state system. Let's go to move state.
Move code(this code is in user event 0):
/// @description Move State
var _x_input = keyboard_check(vk_right) - keyboard_check(vk_left);
var _y_input = keyboard_check(vk_down) - keyboard_check(vk_up);
var _direction_degree = point_direction(0,0,_x_input, _y_input);
//direction where we go now
var _x_speed = lengthdir_x(speed_, _direction_degree); //get necessary y and x speed values
var _y_speed = lengthdir_y(speed_, _direction_degree);
if (_x_input == 0 and _y_input == 0) { //player stay
image_speed = 0;
image_index = 0;
}
if (_x_input != 0 or _y_input != 0) { //player moves
direction_facing_ = scr_get_direction_facing(_x_input, _y_input);
//check move direction to change sprite
image_speed = 1;
if(_x_input == -1)
image_xscale = -1;
else
image_xscale = 1;
x += _x_speed;
y += _y_speed;
}
In first to rows we get kind of coordinate moving direction depends on what buttons we press. If we move left _x_input = -1, if right - _x_input = 1. The same with _y_input. After we get X axis and Y axis parts of our "speed vector", because if we just add speed in necessary direction and move diagonally, than we move too fast and it looks weird, that's why we want to get X and Y component of our speed in direction we need. In general, that's all.
----------------------------------------------TWO------------------------------------------------------
Briefly. Player get some speed value and direction for moving. Speed gets lower by subtracting some small value until player stops. When player stops, state changes to move state. At the same time I create player copies on position player moved from. After some little time these copies moves to player's position by the same way.
Code(user event 0, move state):
if (keyboard_check_pressed(vk_space)) { //expression to jump
clone_move_direction_ = point_direction(x,y,mouse_x,mouse_y);
scr_set_move_values(self, clone_move_direction_, 6, 0.3);
previous_x_ = x; //coordinates where clones will spawn
previous_y_ = y;
clone_count_ = 0; //current clone count
state_ = player.jump; //change state to run user event 1
}
At first we need to get direction where to move. We move to our mouse. This script scr_set_move_values is setting values for the next script that moves player. We remember our previous coordinates and than switch to jump state.
scr_set_move_values:
///@arg object
///@arg direction
///@arg speed
///@arg friction
obj_ = argument0; //object that will be moved
move_direction_ = argument1; //direction
move_speed_ = argument2; //speed
friction_ = argument3; //value we will subtract from move_speed_
Code(user event 1, jump state):
/// @description Jump State
scr_move_object_to();
if(clone_count_ < 2 and alarm[0] <= 0) {
//create player copies that will follow him
instance_create_layer(previous_x_, previous_y_, "Instances", o_player_jump);
alarm[0] = global.one_second/20;
clone_count_++;
}
if (move_speed_ <= 0) //get back to move state
state_ = player.move;
Here you can see some script called scr_move_object_to. This script moves player.
Below script is code that creates 2 player copies every 1/20 second.
Let's look at scr_move_object_to:
var _x_speed = lengthdir_x(move_speed_, move_direction_);
var _y_speed = lengthdir_y(move_speed_, move_direction_);
if (move_speed_ > 0) {
obj_.x += _x_speed;
obj_.y += _y_speed;
move_speed_ = scr_approach(move_speed_, 0, friction_);
}
We get X and Y component of our speed in direction we need, like in move state, and move our player while speed > 0.
Script scr_approach just helps reach some value.
scr_approach:
///@arg value
///@arg target
///@arg friction
var _current = argument0; //starting value
var _target = argument1; //value we want to approach to
var _friction = argument2;
if(_current < _target)
return min(_current + _friction, _target);
else
return max(_current - _friction, _target);
Well, when our "jump speed" approaches 0, we get back to move state.
Let's look at object of player copies.
It's primitive object. It just creates, moves to player object after some time and then destroys itself.
Create event:
scr_set_move_values(self, o_player.clone_move_direction_ , 6, 0.3);
allowed_to_move_ = false;
alarm[0] = global.one_second/30;
Step event:
if (not instance_exists(self))
exit;
if (allowed_to_move_ == true)
scr_move_object_to();
if(move_speed_ <= 0)
instance_destroy();
Alarm[0]:
----------------------------------------------THREE------------------------------------------------------
Sprite changing based on direction player goes. In create event of player object I've created enumeration called dir(direction). Here is all create event code:
speed_ = 1
enum dir {
right,
up,
left,
down
}
enum player {
//Player states
move,
jump
}
starting_state_ = player.move;
state_ = starting_state_;
direction_facing_ = dir.down;
sprite_[player.move,dir.down] = s_player_run_down;
sprite_[player.move,dir.right] = s_player_run_right;
sprite_[player.move,dir.left] = s_player_run_right;
sprite_[player.move,dir.up] = s_player_run_up;
sprite_[player.jump,dir.down] = s_player_run_down;
sprite_[player.jump,dir.right] = s_player_run_right;
sprite_[player.jump,dir.left] = s_player_run_right;
sprite_[player.jump,dir.up] = s_player_run_up;
All player sprites are contains in 2dimensional array called sprite_. This array helps as change sprite automatically in step event depends on in what direction player moves and what state now is (I don't sure if it is correct to say "now is", correct me if it is incorrect. Hope you understand what I mean).
Step event:
event_user(state_);
sprite_index = sprite_[state_,direction_facing_];
depth = -y;
How do we know what direction now is? Let's get back to move state.
User event 0(move state):
/// @description Move State
var _x_input = keyboard_check(vk_right) - keyboard_check(vk_left);
var _y_input = keyboard_check(vk_down) - keyboard_check(vk_up);
var _direction_degree = point_direction(0,0,_x_input, _y_input);
//direction where we go now
var _x_speed = lengthdir_x(speed_, _direction_degree); //get necessary y and x speed values
var _y_speed = lengthdir_y(speed_, _direction_degree);
if (_x_input == 0 and _y_input == 0) { //player stay
image_speed = 0;
image_index = 0;
}
if (_x_input != 0 or _y_input != 0) { //player moves
direction_facing_ = scr_get_direction_facing(_x_input, _y_input);
//check move direction to change sprite
image_speed = 1;
if(_x_input == -1)
image_xscale = -1;
else
image_xscale = 1;
x += _x_speed;
y += _y_speed;
}
Don't look at _direction_degree, we need it to get Xspeed and Yspeed. In second if statement you can see script scr_get_direction_facing. This script gives us the information about the direction we move.
scr_get_direction_facing:
//@arg x_input
//@arg y_input
var _x = argument0
var _y = argument1
var _direction = round(point_direction(0,0,_x,_y) / 90)
if _direction == 4
_direction = 0
return _direction
It works the same way like getting _direction_degree, but here we divide degrees by 90. That is to say that we divide our trigonometric circle to 4 parts, like 4 direction: right, up, left, down. After getting this direction we need to correct this value, because 4 and 0 are the same directions. After all of this calculations we get necessary sprite in step event.
Also some words about this block of code(user event 0, move state):
if (_x_input == 0 and _y_input == 0) { //player stay
image_speed = 0;
image_index = 0;
}
if (_x_input != 0 or _y_input != 0) { //player moves
direction_facing_ = scr_get_direction_facing(_x_input, _y_input);
//check move direction to change sprite
image_speed = 1;
if(_x_input == -1)
image_xscale = -1;
else
image_xscale = 1;
x += _x_speed;
y += _y_speed;
}
First if statement works, when player doesn't move, so we need to stop animating and set first frame of sprite as default. Second statement works when player moves, so we need to continue our animation(image_speed = 1) and check our direction on X axis. Depends on what direction on X axis we have we rotate our sprite in necessary direction(image_xscale).