Пишу игру на 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() {
//** ... **//
}