Как сделать игру платформер на флэше при помощи ActionScript 3: часть вторая

Это вторая версия урока по созданию игры-платформера на языке ActionScript3 в программе AdobeFlash. Можно сказать, что это продолжение первой части, но в этот раз код проекта пересмотрим и изменим, добавим новый функционал: столкновения от левой и правой сторон, двигающихся врагов, систему начисления счета в игре, скроллинг. Хотелось бы обратить внимание, что такой код для игры-платформера можно легко реализовать используя другие языки программирования, так как основная суть алгоритмов схожая, разница лишь в особенностях того или иного программного инструмента.

В этом втором уроке рассмотрим следующие особенности игры-платформера:

  1. Создание объектов-платформ
  2. Враги и их движение по платформам
  3. Реализация системы начисления счета
  4. Проверка столкновений при помощи hitTestObject() и определение стороны
  5. Ограничение гравитации и исправление пролетов через объекты-платформы
  6. Движущиеся платформы, их взаимодействие с игроком
  7. Скорллинг в игре-платформере
  8. Подведение итогов и код игры-платформера

Создание объектов и добавление их в списки

В прошлой версии все объекты добавлялись каждый в свой список при помощи кода, который мы писали внутри символов.

Платформы в список list_platforms:

var game: MovieClip = parent as MovieClip;
game.list_platforms.push(this);

Монеты в список list_coins:

var game: MovieClip = parent as MovieClip;
game.list_coins.push(this);

Так как код усложняется, то возникла необходимость добавления новых объектов, например, таких как enemy, которые нужно настраивать на основе платформ. Сейчас объекты размещаются на сцене вразброс, и код, который обрабатывается для каждого объекта, также выполняется не по порядку. А перед тем, как настроить объекты, взаимодействующие с платформами, нужно сначала добавить в список платформы. Поэтому создадим функцию init(), которая будет вызываться в начале, где настроим игровые объекты. Также создайте новый список для хранения новых объектов — противников:

var list_enemies: Array = new Array();

В начале этой функции будут проверяться все объекты, расположенные на сцене, в зависимости от их типа они будут помещаться в нужный список. В следующем цикле проводим настройку всех платформ, например, установим их все как неподвижные платформы. Так как объекты теперь будут добавляться в списки в этой функции, поэтому удалите код внутри символов Coin и Platform, или просто установите как комментированный текст.

function init(){
  for (var i:uint = 0; i < numChildren; i++){
    if (getChildAt(i) is Platform){
      list_platforms.push(getChildAt(i));
    }
		 
    if (getChildAt(i) is Coin){
      list_coins.push(getChildAt(i));
    }
		 
    if (getChildAt(i) is Enemy){
      list_enemies.push(getChildAt(i));
    }	
  }

  for ( i = 0; i < list_platforms.length; i++ ) {
    var platform: MovieClip = list_platforms[ i ];
    platform.moving = false;
  }
}

Чтобы назначить символу свой тип (класс) выберите его в библиотеке и правой кнопкой мыши вызовите контекстное меню, нажмите на «Properties…», затем на галочку «Export for ActionScript», имя класса будет установлено автоматически, нажмите кнопку «ok». Теперь для каждого символа будет назначен свой класс, который также будет отображаться рядом с именем символа в библиотеке.

Свойства объекта символа в AdobeFlash

Враги и их движение по платформам

Для игры-платформера важным элементом являются препятствия, например, такие как враги — бегающие по платформам объекты, с которыми персонажу не стоит соприкасаться, и победить которых можно только прыгнув на них. Давайте добавим новый символ на сцену, это будет небольшой квадрат размером 30*30 пикселей, назовем его Enemy, сразу укажем, что для него нужно использовать класс с таким же названием.

Создайте объект и настройте выравнивание, как показано на изображении.

Создание объекта символа и его выравнивание в AdobeFlash

Поместите противников на сцену так, чтобы они соприкасались с объектом-платформой.

Размещение объектов символов на сцене в AdobeFlash

Примечание. Можно легко поставить персонажа или его противников на платформу указав в настройках «position and size» параметр координаты «y» такой же, как у объекта-платформы. Желательно устанавливать целочисленные значения координат для объектов на сцене.

Теперь нужно настроить противников, чтобы они смогли ходить по платформам и реагировать на игрока. Для этого в функции init() в цикле настройки платформ проверяем, с какой платформой соприкасается объект Enemy, после чего назначаем enemy.lx и enemy.rx значения — это левая и правая стороны платформы, до которых будет перемещаться противник.

for ( i = 0; i < list_platforms.length; i++ ) {
  var platform: MovieClip = list_platforms[ i ];
  platform.moving = false;
  
  for ( var s: int = 0; s < list_enemies.length; s++ ) {
    var enemy: MovieClip = list_enemies[ s ];
    enemy.sp = 1;
    if(platform.hitTestPoint(enemy.x, enemy.y+10) ){
      enemy.lx = platform.x+enemy.width/2;
      enemy.rx = platform.x+platform.width-enemy.width/2;
    }	
  }
}

Чтобы противники смогли двигаться, нужно добавить код в функцию enter_frame(), там, где обрабатываются монеты.

for (i = 0; i < list_enemies.length; i++) {
  var enemy : MovieClip = list_enemies[ i ];
	
  enemy.x+=enemy.sp;
  if (enemy.x>enemy.rx){
    enemy.sp=-1;
  }
  if (enemy.x<enemy.lx){
    enemy.sp=1;
  }	
	
  if (enemy.hitTestObject(char)){
    if (grav>0){
      removeChild(enemy);
      list_enemies.splice(i,1);
      grav = maxJump/2;
    }
    else{
      restart();	
    }
  }
}

В этом цикле происходит обработка всех противников, которые находятся в списке list_enemies. В зависимости от того до какой стороны дошел противник, изменяется значение координаты «x». Если происходит столкновение персонажа с врагом, то проверяем, прыгнул ли персонаж сверху на врага, если это так, то удаляем врага, иначе запускаем функцию, в которой игрок появляется на стартовой позиции.

Добавьте новую функцию для рестарта:

function restart(){
  char.x = start_x;
  char.y = start_y;
  grav = 0;
}

Также в функцию init() перенесите следующий код:

moving_platform.moving = true;
moving_platform.start_y = moving_platform.y;
moving_platform.sp = 1.0;
char.platform = null;

Реализация системы начисления счета

Когда персонаж собирает монеты, то по логике должны начисляться очки. Давайте сделаем отображение этих данных. При помощи text tool создайте на сцене текстовое поле и назовите его, например, tf_score. Затем два раза нажмите на него и введите текст «Score:». Вы также можете установить цвет текста для этого текстового поля на панели «Properties».

Примечание. Для корректной работы текстового поля в AdobeFlash следует определить значения, которые будут в нем использоваться. Чтобы это сделать выделите его и на панели «Properties» нажмите на кнопку «Embed…». В «Character ranges» укажите, что будут использоваться все значения.

Для сохранения данных о собранных монетах нужно создать переменную, например:

var score: int;

А для отображения этих данных будем увеличивать переменную score на 100, а затем назначать текстовому полю значения этой переменной:

// coins
for (i = 0; i < list_coins.length; i++) {
  var coin : MovieClip = list_coins[ i ];
  if (coin.hitTestObject(char)){
    score = score + 100;	
    tf_score.text = "Score: "+String(score);
    removeChild(coin);
    list_coins.splice(i,1);
  }
}

Проверка столкновений при помощи hitTestObject() и определение стороны

В текущем примере происходит проверка столкновений, если персонаж над платформой или прыгает снизу. Чтобы добавить возможность препятствий слева и справа нужно поменять алгоритм проверки столкновений.

Заменим строку:

if(platform.hitTestPoint( char.x-15, char.y ) || platform.hitTestPoint( char.x+15, char.y )){

на следующий код:

if(platform.hitTestObject( char )){
  if (char.y-grav<platform.y && char.x+15>platform.x && char.x-15<platform.x+platform.width && grav>0){

Сначала будет происходить проверка столкновений персонажа с объектами-платформами, а затем будем проверять сторону столкновения.

if(platform.hitTestObject( char )){	
  // персонаж прыгает сверху на объект-платформу
  if (char.y-grav<platform.y && char.x+15>platform.x && char.x-15<platform.x+platform.width){
    char.platform = platform;
    char.y = platform.y;
    grav = 0;
    touchingGround = true;
  }
	  
  // прыгает снизу
  else if(char.y-char.height>platform.y && char.y-char.height<platform.y+platform.height && char.x>platform.x && char.x<platform.x+platform.width && grav<0){
    char.y=platform.y+platform.height+char.height;
    grav=3;  
  }
	  
  // левая сторона объекта
  else if (char.x+15<platform.x+platform.width && char.x+15>platform.x && char.y!=platform.y ){
    char.x = platform.x-15;
  }
	  
  // правая сторона объекта
  else if(char.x-15>platform.x && char.x-15<platform.x+platform.width && char.y!=platform.y){
    char.x = platform.x+platform.width+15;
  }		  
}

Как видите, теперь персонаж также сталкивается с правой и левой стороной платформы. Хотя, это происходит немного странно — сначала происходит столкновение, а затем персонаж как бы отталкивается назад от левой или правой стороны. Чтобы этого не происходило нужно код движения персонажа перенести в начало функции enter_frame.

if (right_key_down) {
  char.x += speed;
}

if (left_key_down) {
  char.x -= speed;
}

Как вы заметили, мы также заменили старый код проверки прыжка снизу:

if (platform.hitTestPoint(char.x, char.y - (char.height))) {

на следующий код:

else if(char.y-char.height>platform.y && char.y-char.height<platform.y+platform.height && char.x>platform.x && char.x<platform.x+platform.width && grav<0){

Строку touchingGround = false; запишем в условие проверки нажатия кнопки прыжка и переместим этот код в начало функции enter_frame.

if (jump_key_down && touchingGround) {
  grav = maxJump;
  touchingGround = false;
}

Ограничение гравитации и исправление пролетов через объекты-платформы

В играх жанра «платформер» существуют свои особенности, например, может неправильно работать проверка столкновений, если она происходит с объектами, которые движутся и имеют небольшой размер. Это связано с тем, что определение столкновений происходит каждый кадр, и если объект увеличивает свое значение по координате на 10 пикселей и имеет размер такой же или меньше, то он может не соприкоснуться с другим объектом.

Пролеты объекта и несрабатывание проверки столкновений

Если сделать высоту платформы меньше, а скорость гравитации больше, то персонаж будет «пролетать» и не будет происходить столкновения. Сейчас персонаж прыгает очень быстро, предлагаю уменьшить гравитацию и силу прыжка.

var gravity:Number = 1;
var maxJump:Number = -15;

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

// персонаж стоит или прыгает сверху на объект-платформу
  if (char.y-grav<platform.y && char.x+15>platform.x && char.x-15<platform.x+platform.width && grav>0 ){

Движущиеся платформы, их взаимодействие с игроком

Можно сказать, что в проекте есть еще небольшой «баг» , который будет происходить, если персонаж стоит на неподвижной платформе, а снизу будет подниматься движущаяся платформа, то она не поднимет персонажа, просто «оттолкнет» его. Также персонаж может скатываться с платформы, если стоит на движущейся, и она опускается, это зависит от того, в какой последовательности эти два объекта-платформы находятся в списке. Установите две платформы, как показано на рисунке и проверьте работу программы.

Проверка взаимодействия персонажа с объектами-платформами в AdobeFlash

Такое поведение происходит из-за того, что платформы в списке проверяются друг за другом, и, в зависимости от их последовательности, приоритет отдается какой-либо из списка. Чтобы это поправить, нужно внести некоторые изменения в код проверки столкновений.

Создадим массив list_platforms2 в функции enter_frame и будем добавлять в него платформы, на которые прыгнул персонаж, а затем определять, к какой из этих платформ его прикрепить.

var list_platforms2: Array = new Array();

for ( var i : int = 0; i < list_platforms.length; i++ ) {var platform: MovieClip = list_platforms[ i ];	
  if(platform.hitTestObject( char )){	
    if (char.y-grav<platform.y && char.x+15>platform.x && char.x-15<platform.x+platform.width && grav>0 ){
      //определить все пересечения, а затем назначить char на правильный объект
      list_platforms2.push(platform);
    }
	  
    else if(char.y-char.height>platform.y && char.y-char.height<platform.y+platform.height && char.x>platform.x && char.x<platform.x+platform.width && grav<0){
      char.y=platform.y+platform.height+char.height;
      grav=3;  
    }
	  
    // левая сторона объекта
    else if (char.x+15<platform.x+platform.width && char.x+15>platform.x && char.y!=platform.y ){
      char.x = platform.x-15;
    }
	  
    // правая сторона объекта
    else if(char.x-15>platform.x && char.x-15<platform.x+platform.width && char.y!=platform.y){
      char.x = platform.x+platform.width+15;
    }	
  }  
}

for ( i = 0; i < list_platforms2.length; i++ ) {
  if(list_platforms2[i].y <= char.y){
    char.platform = list_platforms2[i];	
    char.y = list_platforms2[i].y;
    grav = 0;
    touchingGround = true;
  }
}

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

Скорллинг в игре-платформере

Скроллинг или прокрутка в игре платформере — это движение сцены и всего, что находится на уровне за персонажем, с тем, чтобы показать то, что не умещается на экране. Во многих играх уровни могут выходить за пределы экрана, поэтому для движения игрового мира используется технология вертикальной и горизонтальной прокрутки (скроллинга). Когда персонаж двигается, то происходит движение игрового уровня в противоположную сторону, создается иллюзия того, что камера следит за движением персонажа. Если сказать простыми словами, то нужно двигать все содержимое уровня в зависимости от движения персонажа, что мы и сделаем.

Для начала давайте нарисуем задний фон, который будет находиться за всеми объектами в игре, и который не будет передвигаться. Создайте новый символ, назовите его «Bg» и нарисуйте в нем прямоугольник с такими же размерами, как сцена 550*400 пикселей. Заполните этот объект градиентной заливкой, например, такой:

Заполнение градиентной заливкой в AdobeFlash

Теперь возьмите и установите этот символ на сцену, так чтобы он находился позади других объектов. В свойствах Instance Name напишете «bg».

Рисунок заднего фона для игры-платформера в AdobeFlash

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

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

var viewport_width: int = 550;

При движении персонажа будет происходить центрирование и перемещение сцены.

this.x = - char.x + viewport_width/2;

Также нужно привязать задний фон к координатам персонажа и текстовое поле счета к экрану.

bg.x = char.x;
tf_score.x = -this.x+390;

Этот способ скроллинга для игры-платформера самый простой. Можно использовать условные границы, с которыми будет взаимодействовать персонаж, и происходить прокрутка уровня. Также лучше всего поместить все объекты в один и двигать уже его, а движение обрабатывать отдельными функциями.

Подведение итогов и код игры-платформера

В этом уроке мы рассмотрели, как сделать и улучшить игру-платформер на флэш используя ActionScript 3. Реализовали простой скроллинг игрового уровня, добавили двигающихся врагов и пересмотрели систему столкновений с объектами в игре-платформере. Как и в первом уроке, этот код еще можно дорабатывать и оптимизировать, улучшить поведение врагов, добавить новые бонусы и сделать продуманную горизонтальную и вертикальную прокрутку игрового уровня с дополнительными возможностями.

Далее приведен весь код игры платформера, вы также можете скачать исходники здесь.

import flash.events.MouseEvent;
var start_x:int = 50;
var start_y:int = 150;

var viewport_width: int = 550;
var grav:Number = 0; 
var gravity:Number = 1; 
var speed:Number = 2; 
var maxJump:Number = -15; 
var touchingGround:Boolean = false;
var score: int;

var list_platforms: Array = new Array();
var list_coins: Array = new Array(); 
var list_enemies: Array = new Array();

var left_key_down: Boolean = false;
var right_key_down: Boolean = false;
var jump_key_down: Boolean = false;

addEventListener( Event.ENTER_FRAME, enter_frame );
stage.addEventListener( KeyboardEvent.KEY_DOWN, on_keydown );
stage.addEventListener( KeyboardEvent.KEY_UP, on_keyup );

init();

function enter_frame ( e: Event ) : void {
  var list_platforms2: Array = new Array();
  
  if (jump_key_down && touchingGround) {
    grav = maxJump;
    touchingGround = false;
  }
	
  if (right_key_down) {
    char.x += speed;
  }
		
  if (left_key_down) {
    char.x -= speed;
  }
  
  this.x = - char.x + viewport_width/2;
  bg.x = char.x;
  tf_score.x = -this.x+390;
  
  char.y += grav;
  grav += gravity;

  for ( var i : int = 0; i < list_platforms.length; i++ ) {	
    var platform: MovieClip = list_platforms[ i ];
	  
    if (platform.moving){
      platform.y += platform.sp;
			
      if (char.platform == platform){	
        char.y += platform.sp;
      }
							
      if (platform.y>=platform.start_y+50 || platform.y<=platform.start_y-50){
        platform.sp=platform.sp*-1;
	  }
	}

	if(platform.hitTestObject( char )){	
      // персонаж стоит или прыгает сверху на объект-платформу
  	  if (char.y-grav<platform.y && char.x+15>platform.x && char.x-15<platform.x+platform.width && grav>0 ){
		list_platforms2.push(platform);
      }
      // прыгает снизу
      else if(char.y-char.height>platform.y && char.y-char.height<platform.y+platform.height && char.x>platform.x && char.x<platform.x+platform.width && grav<0){
        char.y=platform.y+platform.height+char.height;
        grav=3;  
      }
	  
      // левая сторона объекта
      else if (char.x+15<platform.x+platform.width && char.x+15>platform.x && char.y!=platform.y ){
        char.x = platform.x-15;
      }
	  
      // правая сторона объекта
      else if(char.x-15>platform.x && char.x-15<platform.x+platform.width && char.y!=platform.y){
        char.x = platform.x+platform.width+15;
      }		
	}

  }

  for ( i = 0; i < list_platforms2.length; i++ ) {
    if(list_platforms2[i].y <= char.y){
      char.platform = list_platforms2[i];	
      char.y = list_platforms2[i].y;
      grav = 0;
      touchingGround = true;
    }
  }

  for (i = 0; i < list_coins.length; i++) {
    var coin : MovieClip = list_coins[ i ];
    if (coin.hitTestObject(char)){
	  score = score + 100;	
      tf_score.text = "Score: "+String(score);
      removeChild(coin);
      list_coins.splice(i,1);
    }
  }

  for (i = 0; i < list_enemies.length; i++) {
    var enemy : MovieClip = list_enemies[ i ];	
    enemy.x+=enemy.sp;
    if (enemy.x>enemy.rx){
      enemy.sp=-1;
    }
    if (enemy.x<enemy.lx){
      enemy.sp=1;
    }	
	
    if (enemy.hitTestObject(char)){
      if (grav>0){
        removeChild(enemy);
        list_enemies.splice(i,1);
        grav = maxJump/2;
      }
      else{
        restart();	
      }
    }
  }

  if (char.y > 450) {
    char.x = start_x;
    char.y = start_y;
    grav = 0;
  }
}

function on_keydown( e : KeyboardEvent ) : void {
  //LEFT Key 
  if (e.keyCode == 37) {
    left_key_down = true;
  }
  //RIGHT Key
  else if (e.keyCode == 39) {
	right_key_down = true;
  }
  //Up Key
  else if (e.keyCode == 38) {
	jump_key_down = true;
  }
}

function on_keyup( e : KeyboardEvent ) : void {
  //LEFT Key  
  if (e.keyCode == 37) {
    left_key_down = false;
  }
  //RIGHT Key
  else if (e.keyCode == 39) {
    right_key_down = false;
  }
  //Up Key
  else if (e.keyCode == 38) {
    jump_key_down = false;
  }
}

function init(){
  for (var i:uint = 0; i < numChildren; i++){
    if (getChildAt(i) is Platform){
      list_platforms.push(getChildAt(i));
    }
		 
    if (getChildAt(i) is Coin){
      list_coins.push(getChildAt(i));
    }
		 
    if (getChildAt(i) is Enemy){
      list_enemies.push(getChildAt(i));
    }	
  }

  for ( i = 0; i < list_platforms.length; i++ ) {
    var platform: MovieClip = list_platforms[ i ];
    platform.moving = false;
	
    for ( var s: int = 0; s < list_enemies.length; s++ ) {
      var enemy: MovieClip = list_enemies[ s ];
      enemy.sp = 1;
      if(platform.hitTestPoint(enemy.x, enemy.y+10) ){
        enemy.lx = platform.x+enemy.width/2;
        enemy.rx = platform.x+platform.width-enemy.width/2;
      }	
    }	
  }
  
  moving_platform.moving = true;
  moving_platform.start_y = moving_platform.y;
  moving_platform.sp = 1.0;
  char.platform = null;
  
}

function restart(){
  char.x = start_x;
  char.y = start_y;
  grav = 0;
}

Если у вас есть вопросы (предложения) или вы хотите продолжения урока, то пишите в комментариях. Поделитесь этим уроком и добавьте его в закладки, чтобы сохранить в социальных сетях — нажмите на значки внизу.

Автор: Jean Winters

Один комментарий на “Как сделать игру платформер на флэше при помощи ActionScript 3: часть вторая

  1. Можно было бы объекты добавить в один мувиклип и уже его двигать, так было бы понятнее и проще, хотя и так нормально, в принципе.

Комментировать

Почта не публикуется.Обязательные поля отмечены *