|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 3 weeks ago |
README.md
Създаване на космическа игра Част 1: Въведение
Тест преди лекцията
Наследяване и композиция в разработката на игри
В предишните уроци не беше необходимо да се замисляте много за архитектурата на приложенията, които създавахте, тъй като проектите бяха с малък обхват. Но когато приложенията ви нараснат по размер и сложност, архитектурните решения стават по-важни. Има два основни подхода за създаване на по-големи приложения в JavaScript: композиция или наследяване. И двата подхода имат своите предимства и недостатъци, но нека ги разгледаме в контекста на игра.
✅ Една от най-известните книги за програмиране е свързана с дизайн модели.
В една игра имате обекти на играта
, които са обекти, съществуващи на екрана. Това означава, че те имат местоположение в декартова координатна система, характеризирано с координати x
и y
. Докато разработвате игра, ще забележите, че всички обекти на играта имат стандартни свойства, общи за всяка игра, която създавате, а именно елементи, които са:
- базирани на местоположение Повечето, ако не всички, елементи на играта са базирани на местоположение. Това означава, че те имат местоположение,
x
иy
. - подвижни Това са обекти, които могат да се преместят на ново местоположение. Обикновено това е герой, чудовище или NPC (персонаж, който не е играч), но не например статичен обект като дърво.
- самоунищожаващи се Това са обекти, които съществуват само за определен период от време, преди да се подготвят за изтриване. Обикновено това се представя чрез булева стойност
dead
илиdestroyed
, която сигнализира на игровия двигател, че този обект вече не трябва да се визуализира. - с временно ограничение 'Временно ограничение' е типично свойство за краткотрайни обекти. Типичен пример е текст или графичен ефект като експлозия, който трябва да се вижда само за няколко милисекунди.
✅ Помислете за игра като Pac-Man. Можете ли да идентифицирате четирите типа обекти, изброени по-горе, в тази игра?
Изразяване на поведение
Всичко, което описахме по-горе, представлява поведение, което обектите на играта могат да имат. Как можем да кодираме това? Можем да изразим това поведение като методи, свързани с класове или обекти.
Класове
Идеята е да използваме класове
в комбинация с наследяване
, за да добавим определено поведение към даден клас.
✅ Наследяването е важна концепция за разбиране. Научете повече от статията на MDN за наследяването.
Изразено чрез код, обект на играта обикновено изглежда така:
//set up the class GameObject
class GameObject {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
}
//this class will extend the GameObject's inherent class properties
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
}
//this movable object can be moved on the screen
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//this is a specific class that extends the Movable class, so it can take advantage of all the properties that it inherits
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
}
}
//this class, on the other hand, only inherits the GameObject properties
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
}
}
//a hero can move...
const hero = new Hero();
hero.moveTo(5,5);
//but a tree cannot
const tree = new Tree();
✅ Отделете няколко минути, за да си представите герой от Pac-Man (например Inky, Pinky или Blinky) и как би бил написан на JavaScript.
Композиция
Друг начин за обработка на наследяването на обекти е чрез използване на композиция. Тогава обектите изразяват своето поведение по следния начин:
//create a constant gameObject
const gameObject = {
x: 0,
y: 0,
type: ''
};
//...and a constant movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//then the constant movableObject is composed of the gameObject and movable constants
const movableObject = {...gameObject, ...movable};
//then create a function to create a new Hero who inherits the movableObject properties
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
}
//...and a static object that inherits only the gameObject properties
function createStatic(x, y, type) {
return {
...gameObject
x,
y,
type
}
}
//create the hero and move it
const hero = createHero(10,10);
hero.moveTo(5,5);
//and create a static tree which only stands around
const tree = createStatic(0,0, 'Tree');
Кой модел да използвам?
Изборът зависи от вас. JavaScript поддържа и двата подхода.
--
Друг модел, често срещан в разработката на игри, се занимава с проблема за управлението на потребителското изживяване и производителността на играта.
Модел Pub/Sub
✅ Pub/Sub означава 'публикуване-абониране'
Този модел разглежда идеята, че различните части на вашето приложение не трябва да знаят една за друга. Защо? Това прави много по-лесно да се разбере какво се случва като цяло, ако различните части са разделени. Освен това улеснява внезапната промяна на поведението, ако е необходимо. Как постигаме това? Чрез установяване на няколко концепции:
- съобщение: Съобщението обикновено е текстов низ, придружен от незадължителен полезен товар (част от данни, която уточнява за какво е съобщението). Типично съобщение в игра може да бъде
KEY_PRESSED_ENTER
. - публикуващ: Този елемент публикува съобщение и го изпраща до всички абонати.
- абонат: Този елемент слуша за конкретни съобщения и изпълнява някаква задача в резултат на получаването на това съобщение, като например изстрелване на лазер.
Реализацията е доста малка по размер, но е много мощен модел. Ето как може да бъде реализиран:
//set up an EventEmitter class that contains listeners
class EventEmitter {
constructor() {
this.listeners = {};
}
//when a message is received, let the listener to handle its payload
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//when a message is sent, send it to a listener with some payload
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
}
}
}
За да използваме горния код, можем да създадем много малка реализация:
//set up a message structure
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
};
//invoke the eventEmitter you set up above
const eventEmitter = new EventEmitter();
//set up a hero
const hero = createHero(0,0);
//let the eventEmitter know to watch for messages pertaining to the hero moving left, and act on it
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
//set up the window to listen for the keyup event, specifically if the left arrow is hit, emit a message to move the hero left
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
});
Горе свързваме събитие от клавиатурата, ArrowLeft
, и изпращаме съобщението HERO_MOVE_LEFT
. Слушаме това съобщение и местим hero
в резултат. Силата на този модел е, че слушателят на събития и героят не знаят един за друг. Можете да пренастроите ArrowLeft
към клавиша A
. Освен това би било възможно да направите нещо напълно различно при ArrowLeft
, като направите няколко редакции на функцията on
на eventEmitter:
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
Когато играта ви стане по-сложна, този модел остава със същата сложност, а кодът ви остава чист. Силно се препоръчва да възприемете този модел.
🚀 Предизвикателство
Помислете как моделът pub-sub може да подобри една игра. Кои части трябва да излъчват събития и как играта трябва да реагира на тях? Сега е вашият шанс да бъдете креативни, като измислите нова игра и как нейните части могат да се държат.
Тест след лекцията
Преглед и самостоятелно обучение
Научете повече за Pub/Sub, като прочетете за него.
Задание
Отказ от отговорност:
Този документ е преведен с помощта на AI услуга за превод Co-op Translator. Въпреки че се стремим към точност, моля, имайте предвид, че автоматичните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия изходен език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален превод от човек. Ние не носим отговорност за каквито и да е недоразумения или погрешни интерпретации, произтичащи от използването на този превод.