@Arrow_MGD

Как архитектурно правильно организовать классы в игре на JS. (ООП)?

Пишу игру на JS в стиле Tower Defence. Графику реализовываю через Vue.js, решил не через Canvas. Поскольку решил это делать, чтобы чуть больше понять в ООП, возник ряд вопросов. Реализовал уже много механик, но из-за огромного количества файлов, при рефакторинге задался вопросами, а так ли я все организовываю.

Вопросы следующие:

1. Есть класс игры, который реализовывает логику работы с requestAnimationFrame. Тут же есть repeatFunction, которая каждый тик повторяет определенные методы.
//** game.js **//
export default class Game {
	constructor() {
		this.id = Date.now().toString(36) + Math.random().toString(36).substr(2);
		this.waves = reactive(new Wave());
		this.tower = reactive(new Tower());
		//** ... **//
		this.initGame();
	}

	repeatFunction = (dt) => {
		this.waves.tick(dt, this.tower);
		this.waves.spawnEnemies();
		Enemy.enemiesMove(dt, this.waves.enemies, this.tower);
		this.tower.towerTick(dt, this.perimeter, this.waves.enemies);
		this.tower.towerBulletsMove(this.waves.enemies, this.perimeter);
		Enemy.enemiesDeath(this.tower, this.waves);
		Enemy.enemiesAttack(dt, this.waves, this.tower);
		//** ... **//
	};

	initGame = () => {
		//** Логика requestAnimationFrame **//
	};
}

Есть башня, есть волны, есть враги. Где правильно хранить экземпляры классов врагов? На данный момент у меня реализовано таким образом: в классе игры создан экземпляр класса волны (там реализован таймер волны, передышки и спавн врагов по этому таймеру). В экземпляре волны есть массив с врагами, который наполняется экземплярами от врагов. В самом классе врагов реализованы обычные методы движения, атаки и статические методы (например движение и атаки всех врагов за этот тик).
//** wave.js **//
export default class Wave {
	constructor() {
		this.enemies = ref([]);
		this.sluges = ref([]);
		this.wave = ref(0);
		this.long = ref(useGameSettings().gameSettings.waveLong);
		this.delay = ref(useGameSettings().gameSettings.waveDelay);
		//** ... **//
		this.startNewWave();
	}

	startNewWave = () => {
		this.wave.value += 1;
		this.long.value = useGameSettings().gameSettings.waveLong;
		this.delay.value = useGameSettings().gameSettings.waveDelay;
		//** ... **//
	};

	tick = (dt, tower) => {
		if (this.long.value > 0) return (this.long.value -= dt);
		if (this.delay.value >= 0) return (this.delay.value -= dt);
		this.startNewWave();
		//** ... **//
	};

	spawnEnemies = () => {//** ... **//};
}

//** enemy.js **//
export class Enemy {
	constructor(enemiesParams) {
		[this.x, this.y] = getStartPosition(this.size);
		Enemy.getStartStats(this, enemiesParams);
		//** ... **//
	}

	static getStartStats(enemy, enemiesParams) {
		enemy.s_hp = ref(enemiesParams.hp);
		//** ... **//
	}

	move = (tower) => {//** ... **//};
	attack = (dt, tower) => {//** ... **//};
	getDamage = (damage) => {//** ... **//};
	death = (tower) => {//** ... **//};

	static getEnemiesParams(gameLvl, waveCount) {//** ... **//}
	static getRandomEnemy(enemiesParams) {//** ... **//}
	static enemiesMove(dt, enemies, tower) {//** ... **//}
	static enemiesCollision(enemies, enemy) {//** ... **//}
	static enemiesAttack(dt, waves, tower) {//** ... **//}
	static enemiesDeath = (tower, waves) => {//** ... **//}
}

Правильно же я понимаю, что статические методы класса как раз для оперирования с несколькими экземплярами класса? Может правильнее было бы создать в классе врагов статический массив и там хранить экземпляры врагов? И уже обращаться во всей игре, где есть с ними взаимодействие через этот статический массив класса? Ну и так реализовать все сущности, которые повторяются (волны, пули летящие от башни)?

2. Как быть с единичными сущностями? Ну вот башня у меня одна. Создать статическую переменную в классе башни, или все же создать экземпляр башни и хранить его в классе игры?


3. Класс башни очень большой, поскольку в нем много механик (и критический урон, и отбрасывание врагов и т.п.). Но вот некоторые функции у меня находятся не в самом классе. Например. У башни есть массив с пулями которые она выпускает. Каждый раз, когда выпускается пуля, идёт просчет будет ли урон критическим. И эта вот функция просчёта у меня просто в файле. Нужно ли её загонять в класс как метод? И если загонять, то как обычный метод или статический?

//** tower.js **//
export default class Tower {
	constructor() {
		this.bullets = ref([]);

		this.dollars = ref(5000000);

		this.s_hp = ref(useUpdates().updates['defence'].groups['basic'].updates['hp'].getCount());
		this.s_hp_max = computed(() => useUpdates().updates['defence'].groups['basic'].updates['hp'].getCount());
		this.s_damage = computed(() => useUpdates().updates['attack'].groups['basic'].updates['damage'].getCount());
		this.s_cooldown = ref(0);
	}

	towerTick = (dt, perimeter, enemies) => {//** ... **//};

	towerRegenHP = (dt) => {//** ... **//};

	towerTakeShot = (targetsForShot) => {
		//** !!! **//
		this.bullets.value.push(new Bullet(targetsForShot[i].id, calculateDamageWithCrit(this.s_damage.value)));
	};

	towerBulletsMove = (enemies, perimeter) => {//** ... **//};

	getDamage = (damage) => {//** ... **//};

	//** ...И прочие механики... **//
}

function getTargetsForShot(enemies, bullets) {
	//** ... **//
}

function calculateDamageWithCrit(damage) {
	//** ... **//
}

function calculateAttackSpead() {
	//** ... **//
}
  • Вопрос задан
  • 189 просмотров
Решения вопроса 1
@Mercury13
Программист на «си с крестами» и не только
Статические методы класса как раз для оперирования с несколькими экземплярами класса?

Статические поля и методы служат для работы с классом в целом. Примеры:
• Есть десять объектов и новых создавать нельзя.
• Общий для всей проги объектный пул, а второго, скорее всего, не будет.
• Псевдоконструктор — Rect.xyxy(x1,y1,x2,y2) и Rect.xywh(x,y,w,h).
• Функциональность никак не зависит от экземпляра объекта: calculateDamageWithCrit при условии, что генератор случайных чисел тоже статический (принадлежит библиотеке языка в целом, а не игровому миру).

Где правильно хранить экземпляры классов врагов?

С Wave вы, вероятно, сами запутались: это то информация о волне, то часть текущего состояния мира. Я бы всех врагов закинул в Game (правда, назвал бы объект World).

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

Башня принадлежит игровому миру — тоже её в Game (или World).

У башни есть массив с пулями которые она выпускает.

Пули очень не стоит в башню — их лучше в тот же Game/World. Башню вы уничтожаете — пули тоже исчезают?

И эта вот функция просчёта у меня просто в файле. Нужно ли её загонять в класс как метод?

Только для красоты. Не важно, где лежит скрытая функция, если она не зависит от экземпляра (типа-статическая) и скрытая (не видна снаружи).

Важная штука: часто в играх есть неизменная информация о врагах, башнях, пулях (TowerType, EnemyType, BulletType, WaveInfo…), и есть конкретный экземпляр (Tower, Enemy, Bullet). Иногда враги, башни и пули объединяют в один GameObj, но это уже зависит от архитектуры. Возможно и так, и этак.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы
24 нояб. 2024, в 01:35
5000 руб./за проект
24 нояб. 2024, в 01:24
500 руб./за проект
24 нояб. 2024, в 00:04
5000 руб./за проект