translatedTitles.Article
OO
Oliver Orangers
3 days ago
Урок 11. Состояния и смерть персонажа

Привет, друзья! Продолжаем наш цикл статей про разработку своего рогалика. На прошлом занятии мы с вами сделали пассивный предметы. Сегодня займемся состояниями нашего игрока, одним из которых будет смерть.

В объекте нашего игрока добавим событие столкновение с врагом. Желательно сделать либо одного противника родителем, или вообще всем противникам сделать отдельный родительский объект, чтобы его использовать тут, и при столкновении с любыми врагами запускалось это событие. В моем случае я пока покажу на гоблине, так как сегодня мы сегодня всё равно будет работать с отдельным скриптом.


Напишем в новосозданном событии следующее:

state = PlayerStateHurt;


Это новая переменная состояния. State. Она будет отвечать за то, какое именно у нас сейчас состояние.

Добавим его и в создать и запишем туда новое состояние PlayerStateFree - оно будет отвечать за всё то, что мы уже накодили. Что если всё в порядке, мы можем ходить и стрелять. А если нас ударили, то будет работать состояние PlayerStateHurt.

Оба этих состояния будут написаны в скриптах.

Напишем новую строчку в "Создать" игрока.


Перед тем как мы начнем писать состояние PlayerStateHurt надо навести порядок в событии "Шаг" игрока:

depth = -bbox_bottom;

//Клавиши управления
keyLeft = keyboard_check(ord("A"));
keyRight = keyboard_check(ord("D"));
keyUp = keyboard_check(ord("W"));
keyDown = keyboard_check(ord("S"));

//Клавиши стрельбы
keyShootLeft = keyboard_check(vk_left);
keyShootRight = keyboard_check(vk_right);
keyShootUp = keyboard_check(vk_up);
keyShootDown = keyboard_check(vk_down);

//Блок стрельбы
if (shootCooldown > 0) shootCooldown--;
if (keyShootLeft) and (shootCooldown <= 0)
{
	shootCooldown = shootTimer;
	with(instance_create_layer(x,y-5,"Bullets",oBullet))
	{
		direction = 180;
	}
}
if (keyShootRight) and (shootCooldown <= 0)
{
	shootCooldown = shootTimer;
	with(instance_create_layer(x,y-5,"Bullets",oBullet))
	{
		direction = 0;
	}
}
if (keyShootUp) and (shootCooldown <= 0)
{
	shootCooldown = shootTimer;
	with(instance_create_layer(x,y-5,"Bullets",oBullet))
	{
		direction = 90;
	}
}
if (keyShootDown) and (shootCooldown <= 0)
{
	shootCooldown = shootTimer;
	with(instance_create_layer(x,y-5,"Bullets",oBullet))
	{
		direction = 270;
	}
}

//Движение
inputDirection = point_direction(0, 0, keyRight - keyLeft, keyDown - keyUp);
inputMagnitude = (keyRight - keyLeft != 0) or (keyDown - keyUp != 0);

//Строчка, которая каждый шаг выполняет наш скрипт состояния
script_execute(state);

Отлично, теперь можно и написать, что же будет в скрипте PlayerStateFree!

Для этого создадим новый скрипт в папке "Скрипты" справа и назовем его "PlayerStateFree":


В нем напишем следующее:

function PlayerStateFree()
{
	//Movement
	hSpeed = lengthdir_x(inputMagnitude * global.speedWalk, inputDirection);
	vSpeed = lengthdir_y(inputMagnitude * global.speedWalk, inputDirection);

	PlayerCollision();

	//Update Sprite Index
	var _oldSprite = sprite_index;
	if (inputMagnitude != 0)
	{
		direction = inputDirection
		sprite_index = spriteRun;
		if keyShootDown 
		{
			direction = 270;
			sprite_index = spriteRunAndFire;
		}
		else if keyShootUp 
		{
			direction = 90;
			sprite_index = spriteRunAndFire;
		}
		else if keyShootLeft 
		{
			direction = 180;
			sprite_index = spriteRunAndFire;
		}
		else if keyShootRight 
		{
			direction = 0;
			sprite_index = spriteRunAndFire;
		}
	}
	else if keyShootDown 
	{
		direction = 270;
		sprite_index = spriteFire;
	}
	else if keyShootUp 
	{
		direction = 90;
		sprite_index = spriteFire;
	}
	else if keyShootLeft 
	{
		direction = 180;
		sprite_index = spriteFire;
	}
	else if keyShootRight 
	{
		direction = 0;
		sprite_index = spriteFire;
	}
	else sprite_index = spriteIdle;
	if (_oldSprite != sprite_index) localFrame = 0;

	//Update Image Index
	PlayerAnimateSprite();
}


Таким образом выходит, что если мы свободны, то можем двигаться, а в иных состояниях уже будет что-то другое выполняться. Понятно? Теперь в "шаге" игрока выполняется state, а вот какой state, зависит от нас. Их может быть много у нас. Но для начала напишем, что будет, если игрока коснется противник. Произойдет PlayerStateHurt. Добавим же такой скрипт:

function PlayerStateHurt()
{
  //Уничтожаем и не даем появиться следующему:
	instance_destroy(oGoblin);
	instance_destroy(oBullet);

	hSpeed = 0;
	vSpeed = 0;
	//Если только начался скрипт
	if (sprite_index != sPlayerDie) && (sprite_index != sPlayerDead)
	{
		//Обнови спрайт и если надо пусть играет звук
		//audio_play_sound(snDeath,10,false,global.soundVolume);
		sprite_index = sPlayerDie;
		image_index = 0;
		image_speed = 0.5;
	}
}


Мы уничтожаем всех врагов, все снаряды, по желанию можно удалять что-то еще, например монетки и предметы. Затем наш код говорит, что скорость нашего игрока равна нулю. И если спрайт еще старый, то заменить его на нужный, в моем случае sPlayerDie и начать воспроизводиться со скоростью 0.5

Давайте посмотрим, что такое sPlayerDie у меня:


Анимация, где игрок просто вращается. Я хочу, чтобы после того, как до игрок получил урон, он начинал крутиться, а скорость анимации уменьшалась, пока не достигнет нуля. Там и будет воскрешение.

Пойдем, допишем код PlayerStateHurt:


Полный код теперь выглядит так:

function PlayerStateHurt()
{
	//Уничтожаем и не даем появиться следующему:
	instance_destroy(oGoblin);
	instance_destroy(oBullet);
	
	hSpeed = 0;
	vSpeed = 0;
	//Если только начался скрипт
	if (sprite_index != sPlayerDie) && (sprite_index != sPlayerDead)
	{
		//Обнови спрайт и если надо пусть играет звук
		//audio_play_sound(snDeath,10,false,global.soundVolume);
		sprite_index = sPlayerDie;
		image_index = 0;
		image_speed = 0.5;
	}

	//Анимация закончилась?
	if (image_index + image_speed > image_number)
	{
		if (sprite_index == sPlayerDie)
		{
			//Скорость игрока уменьшается пока не достигнет нуля
			image_speed = max(0, image_speed - 0.03);
			//Скорость почти достигла нуля
			if (image_speed < 0.07)
			{
				//Возвращаем дееспособное состояние
				state = PlayerStateFree;
				
				//Куда отправляется игрок после воскрешения
				x = room_width/2;
				y = room_height/2;
				
				image_index = 0;
				image_speed = 1.0;
			}
		}
	}
}


Запустим тест же и посмотрим, как это сейчас выглядит. Как можем видеть, после получения урона наш персонаж начинает свою анимацию, а затем освобождается от этого события.


Отлично! Теперь осталось за малым. Надо просто вычитать нашу переменную "жизни" после получения урона. Если жизни останется 0, то игра будет перезапущена и игрок начнет все уровни с самого начала.

Естественно заходим в "создать" oPlayer или oGame и добавляет переменную global.playerLifes.


Теперь зайдем опять в событие "столкновение с противником" игрока.

Там усложним наш код условием:

if state != PlayerStateHurt and state != PlayerStateDead
{
	if global.playerLifes > 0
	{
		global.playerLifes--;
		state = PlayerStateHurt;
	}
	else
	{
		state = PlayerStateDead;
	}
}


Выходит, что если хп больше 0, то будет исполняться скрипт PlayerStateHurt, а если меньше, то PlayerStateDead.

Что же тогда мы напишем в новом скрипте PlayerStateDead?

Да в целом, он похож на PlayerStateHurt, но как несложно догадаться в конце будет функция, которая перезапустит нашу игру - game_restart(). Ну и монстров я удалять не буду, пусть игрок насладиться армией, которая его одолела :D

function PlayerStateDead()
{
	instance_destroy(oBullet);

	hSpeed = 0;
	vSpeed = 0;
	
	//if just arriving in this state
	if (sprite_index != sPlayerDie) && (sprite_index != sPlayerDead)
	{
		//update the sprite
		audio_play_sound(snDeath,10,false,global.soundVolume);
		sprite_index = sPlayerDie;
		image_index = 0;
		image_speed = 0.5;
	}
	
	//Animation ending this frame?
	if (image_index + image_speed > image_number)
	{
		if (sprite_index == sPlayerDie)
		{
			image_speed = max(0, image_speed - 0.03);
			if (image_speed < 0.07)
			{
				image_index = 0;
				sprite_index = sPlayerDead;
				image_speed = 1.0;
			}
		}
		else
		{
			image_speed = 0;
			image_index = image_number-1;
			game_restart();
		}
	}
}


Давайте затестим! Вот такой результат у нас получился, так что теперь мы можем умереть в нашей игре)


Отлично, на сегодня всё, все свободны :D

Нам осталось только сделать для этой системы отображение очков здоровья, да и монетки надо бы тоже увидеть.

На следующем уроке рассмотрим понятие интерфейса(UI) и сделаем его.


Всем peace!


Статью подготовил Oliver Orangers специально для ims.

Ссылка на vk: https://vk.com/oliora

Ссылка на telegram: https://t.me/oliver_orangers

hub.comments