JavaRush /Java блог /Random UA /Розділ "Ігри" на JavaRush: Корисна теорія

Розділ "Ігри" на JavaRush: Корисна теорія

Стаття з групи Random UA
У розділі «Ігри» на JavaRush ви знайдете захоплюючі проекти написання популярних комп'ютерних ігор. Бажаєте створити свою версію популярних «2048», «Сапера», «Змійки» та інших ігор? Це просто. Ми перетворабо написання ігор на покроковий процес. РозділЩоб спробувати себе в ролі гейм-розробника, не обов'язково бути просунутим програмістом, але певний набір Java-знань все ж таки необхідний. Тут ви знайдете інформацію, яка буде корисною при написанні ігор .

1. Спадкування

Робота з ігровим двигуном JavaRush передбачає використання успадкування. Але що робити, якщо ви не знаєте, що таке? З одного боку, слід у цій темі розібратися: вона вивчається на 11 рівні. З іншого боку, двигун спеціально спроектували дуже простим, тому можна обійтися поверхневим знанням спадкування. Отже, що таке успадкування. Якщо дуже спростити, успадкування це зв'язок між двома класами. Один із них стає батьком, а другий – нащадком (класом-спадкоємцем). При цьому клас-батько може навіть не знати, що має класи-нащадки. Тобто. особливої ​​вигоди від наявності класів-спадкоємців не отримує. А ось класу-нащадок успадкування дає багато переваг. І головне з них — у тому, що всі змінні та методи класу-батька з'являються у класі-нащадці, начебто код класу-батька скопіювали у клас-нащадок. Це не зовсім так, але для спрощеного розуміння спадкування піде. Ось кілька прикладів, щоб краще зрозуміти наслідування. Приклад 1:найпростіше успадкування.
public class Родитель {

}
Клас Нащадок успадкований від класу Батько за допомогою ключового слова extends .
public class Потомок extends Родитель {

}
Приклад 2: використання змінних класу-батька.
public class Родитель {

   public int age;
   public String name;
}
Клас Нащадок може використовувати змінні age і name класу Батько , ніби вони оголошені в ньому.
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(name+" "+age);
   }
}
Приклад 3: використання методів класу-батька.
public class Родитель {

   public int age;
   public String name;

   public getName() {
      return name;
   }
}
Клас Нащадок може використовувати змінні та методи класу Батько начебто вони оголошені у ньому. У цьому прикладі ми використовуємо метод getName ().
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}
Ось як виглядає клас Нащадок з погляду компілятора:
public class Потомок extends Родитель {

   public int age; //  унаследованная переменная
   public String name; //  унаследованная переменная

   public getName() { //  унаследованный метод.
      return name;
  }
   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}

2. Перевизначення методів

Іноді бувають ситуації, що ми успадкували наш клас Нащадок від якогось дуже корисного нам класу Батька разом з усіма змінними та методами, але деякі методи працюють не зовсім так, як нам хочеться. Або зовсім не так, як нам не хочеться. Що робити у цій ситуації? Можемо перевизначити метод, який нам не сподобався. Робиться це дуже просто: у нашому класі Нащадки просто оголошуємо метод із такою самою сигнатурою (заголовком), що й метод класу Батька і пишемо у ньому наш код. Приклад 1: перевизначення методу.
public class Родитель {

   public String name;

   public void setName (String nameNew) {
       name = nameNew;
  }

   public getName() {
      return name;
  }
}
Метод printInfo() виведе на екран фразу "Luke, No!"
public class Потомок extends Родитель {

   public void setName (String nameNew) {
       name = nameNew + ",No!!!";
  }

   public void printInfo() {

      setName("Luke");
      System.out.println( getName());
   }
}
Ось як виглядає клас Нащадок з погляду компілятора:
public Потомок extends Родитель {

   public String name; //  унаследованная переменная

   public void setName (String nameNew) { //  Переопределенный метод взамен унаследованного

       name = nameNew + ", No!!!";
   }
   public getName() { //  унаследованный метод.

      return name;
   }
   public void printInfo() {

     setName("Luke");
     System.out.println(getName());
   }
}
Приклад 2: трохи магії успадкування (та перевизначення методів).
public class Родитель {

   public getName() {
      return "Luke";
  }
   public void printInfo() {

     System.out.println(getName());
   }
}
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
  }
}
В даному прикладі: якщо в класі Нащадок не перевизначено метод printInfo(з класу Батька), при виклику цього методу об'єкт класу Нащадок буде викликаний його метод getName(), а не getName()класу Батька.
Родитель parent = new Родитель ();
parent.printnInfo();
Цей код виведено на екран напис "Luke" .
Потомок child = new Потомок ();
child.printnInfo();
Цей код виведено на екран напис "I'm your father, Luke;" .
Ось як виглядає клас Нащадок з погляду компілятора:
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
   }
   public void printInfo() {

     System.out.println(getName());
   }
}

3. Списки

Якщо ви ще не познайомабося зі списками, то вам коротка інформація. Повну інформацію ви можете знайти на 6-7 рівнях курсу JavaRush . Списки мають багато спільного з масивами:
  • можуть зберігати багато даних певного типу;
  • дозволяють отримувати елементи за їх індексом/номером;
  • індекси елементів розпочинаються з 0.
Переваги списків: На відміну від масивів списки можуть динамічно змінювати розмір. Відразу після створення список має розмір 0. У міру додавання елементів до списку його розмір збільшується. Приклад створення списку:
ArrayList<String> myList = new ArrayList<String>(); // создание нового списка типа ArrayList
Значення у кутових дужках – це тип даних, які можуть зберігати список. Ось деякі методи для роботи зі списком:
Код Короткий опис дій коду
ArrayList<String> list = new ArrayList<String>(); Створення нового списку рядків
list.add("name"); Додати елемент до кінця списку
list.add(0, "name"); Додати елемент на початок списку
String name = list.get(5); Отримати елемент за його індексом
list.set(5, "new name"); Змінити елемент за його індексом
int count = list.size(); Отримати кількість елементів у списку
list.remove(4); Видалити елемент зі списку
Більше про списки можете дізнатися з цих статей:
  1. Клас ArrayList
  2. Робота ArrayList в картинках
  3. Видалення елемента зі списку ArrayList

4. Масиви

Що таке матриця? Матриця - не що інше як прямокутна таблиця, яка може бути заповнена даними. Інакше кажучи, це двовимірний масив. Як ви, напевно, знаєте, масиви Java є об'єктами. Стандартний одновимірний масив типу intвиглядає так:
int [] array = {12, 32, 43, 54, 15, 36, 67, 28};
Уявімо це візуально:
0 1 2 3 4 5 6 7
12 32 43 54 15 36 67 28
Верхній рядок вказує адресаи осередків. Тобто щоб отримати число 67, потрібно звернутися до елемента масиву з індексом 6:
int number = array[6];
Тут усе дуже просто. Двовимірний масив є масивом одновимірних масивів. Якщо ви про це чуєте вперше, зупиніться і уявіть це в голові. Двовимірний масив приблизно виглядає так:
0 Одновимірний масив Одновимірний масив
1 Одновимірний масив
2 Одновимірний масив
3 Одновимірний масив
4 Одновимірний масив
5 Одновимірний масив
6 Одновимірний масив
7 Одновимірний масив
У коді:
int [][] matrix = {
{65, 99, 87, 90, 156, 75, 98, 78}, {76, 15, 76, 91, 66, 90, 15, 77}, {65, 96, 17, 25, 36, 75, 54, 78}, {59, 45, 68, 14, 57, 1, 9, 63}, {81, 74, 47, 52, 42, 785, 56, 96}, {66, 74, 58, 16, 98, 140, 55, 77}, {120, 99, 13, 90, 78, 98, 14, 78}, {20, 18, 74, 91, 96, 104, 105, 77} }
0 0 1 2 3 4 5 6 7
65 99 87 90 156 75 98 78
1 0 1 2 3 4 5 6 7
76 15 76 91 66 90 15 77
2 0 1 2 3 4 5 6 7
65 96 17 25 36 75 54 78
3 0 1 2 3 4 5 6 7
59 45 68 14 57 1 9 63
4 0 1 2 3 4 5 6 7
81 74 47 52 42 785 56 96
5 0 1 2 3 4 5 6 7
66 74 58 16 98 140 55 77
6 0 1 2 3 4 5 6 7
120 99 13 90 78 98 14 78
7 0 1 2 3 4 5 6 7
20 18 74 91 96 104 105 77
Щоб отримати значення 47, необхідно звернутися до елементу матриці на адресау [4][2].
int number = matrix[4][2];
Якщо ви помітабо, координати матриці відрізняються від класичної прямокутної системи координат (Декартова система координат). При зверненні до матриці спочатку ви вказуєте y, потім x , тоді як у математиці прийнято спочатку вказувати x (x, y). Можливо, ви запитуєте: «А чому б не перевернути матрицю у своїй уяві і не звертатися до елементів звичним шляхом через (x, y)? Від цього вміст матриці не зміниться». Так, нічого не зміниться. Але у світі програмування до матриць прийнято звертатися у формі "спочатку y, потім x". Це потрібно сприйняти як належне. Тепер давайте поговоримо про проектування матриці на наш двигун (клас Game). Як вам відомо, у двигуна є багато методів, які змінюють клітини ігрового поля за заданими координатами. Наприклад, методsetCellValue(int x, int y, String value). Він встановлює певну клітину з координатами (x, y) значення value. Як ви помітабо, цей метод спочатку приймає саме х, як у класичній системі координат. Аналогічним чином працюють інші методи движка. При розробці ігор часто буде з'являтися необхідність відтворювати стан матриці на екрані. Як це зробити? По-перше, у циклі потрібно перебрати всі елементи матриці. По-друге, для кожного з них викликати метод відображення з ІНВЕРТОВАНИМИ координатами. Приклад:
private void drawScene() {
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            setCellValue(j, i, String.valueOf(matrix[i][j]));
        }
    }
}
Звісно, ​​інверсія працює у двох напрямках. Метод setCellValueможна передати (i, j), але при цьому з матриці взяти елемент [j][i]. Інверсія може здатися трохи складною, але про неї потрібно пам'ятати. І завжди, якщо виникають якісь проблеми, варто взяти папірець із ручкою, накреслити матрицю та відтворити, які процеси з нею відбуваються.

5. Випадкові числа

Як працювати з генератором випадкових чисел? У класі Gameвизначено метод getRandomNumber(int). Під капотом він використовує клас Randomіз пакета java.util, але принцип роботи з генератором випадкових чисел від цього не змінюється. Як аргумент getRandomNumber(int)приймає ціле число. Це буде верхньою межею, яку може повернути генератор. Нижнім кордоном є 0. Важливо! Генератор НІКОЛИ не поверне верхнє граничне число. Наприклад, якщо викликати getRandomNumber(3)він може повернути 0, 1, 2. Як бачите, 3 він повернути не може. Таке використання генератора досить простим, але дуже ефективним у багатьох випадках. Вам потрібно отримати випадкове число в якихось межах: Уявіть, що вам потрібне якесь тризначне число (100..999). Як ви вже знаєте, мінімальне число, що повертається — 0. Значить, вам потрібно буде до нього додати 100. Але в такому випадку необхідно подбати про те, щоб не переступити верхній кордон. Щоб отримати 999 як максимальне випадкове значення, слід викликати методgetRandomNumber(int)з аргументом 1000. Але ми пам'ятаємо про подальше додавання 100: значить і верхню межу слід знизити на 100. Тобто код для отримання випадкового тризначного числа буде виглядати так:
int number = 100 + getRandomNumber(900);
Але для спрощення подібної процедури в движку передбачений метод getRandomNumber(int, int), який як перший аргумент приймає мінімальне для повернення число. Використовуючи цей метод, попередній приклад можна переписати:
int number = getRandomNumber(100, 1000);
Випадкові числа можуть використовуватись для отримання випадкового елемента масиву:
String [] names = {"Андрій", "Валентин", "Сергей"};
String randomName = names[getRandomNumber(names.length)]
Виклик певних подій з певною ймовірністю. У людини ранок починається за можливими сценаріями: Проспів – 50%; Встав вчасно – 40%; Встав на годину раніше, ніж належить - 10%. Уявіть, що ви пишете емулятор ранку. Вам потрібно викликати події з певною ймовірністю. Для цього, знов-таки, треба скористатися генератором випадкових чисел. Реалізації можуть бути різні, але найпростіша повинна відбуватися за таким алгоритмом:
  1. встановлюємо межі, у яких потрібно згенерувати число;
  2. генеруємо випадкове число;
  3. обробляємо отримане число.
Отже, у разі межею буде 10. Викличемо методgetRandomNumber(10)і проаналізуємо, що він може повернути. Повернути він може 10 цифр (від 0 до 9) і кожну однакову ймовірність — 10%. Тепер нам потрібно скомбінувати всі можливі результати та зіставити їх із нашими можливими подіями. Комбінацій може бути дуже багато, залежно від вашої фантазії, але найочевидніша звучить: «Якщо випадкове число лежить у межах [0..4] — виклик події «Проспав», якщо число в межах [5..8] — «Встав вчасно», і тільки якщо число 9, тоді «Встав на годину раніше, ніж належить». Все дуже просто: в межах [0..4] лежить 5 чисел, кожне з яких може повернутись з ймовірністю 10%, що у сумі і буде 50%; в межах [5..8] лежить 4 числа, та 9 — однина, яка утворюється з ймовірністю 10%. У коді вся ця хитромудра конструкція виглядає ще простіше:
int randomNumber = getRandomNumber(10);
if (randomNumber < 5) {
    System.out.println("Проспал ");
} else if (randomNumber < 9) {
    System.out.println("Встал вовремя ");
} else {
    System.out.println("Встал на час раньше положенного ");
}
Взагалі варіантів застосування випадкових чисел може бути дуже багато. Все залежить лише від вашої фантазії. Але найефективніше їх застосовувати, якщо потрібно багаторазово отримувати якийсь результат. Тоді цей результат відрізнятиметься від попереднього. З якоюсь ймовірністю, звісно. На цьому все! Якщо ви хочете дізнатися про розділ "Ігри" більше, ось корисна документація, яка може допомогти.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ