Большой!
6 интересных факта о Шанхайском метро:
1) Метро в Шанхае появилось всего 33 года назад. И является самым быстрорастущим метрополитеном в мире. Так же Шанхайский метрополитен — самый протяженный в мире, длина его линий составляет 896 км (517 станций), что вдвое больше протяжённости Московского. Средний ежедневный пассажиропоток - более 10 миллионов человек.
2) Стоимость проезда в шанхайском метро нефиксированная и зависит от расстояния поездки. При покупке билета пассажир выбирает пункт назначения на экране терминала, оплачивает рассчитанную сумму, и данные о поездке записываются на пластиковую карту. На входе карта открывает турникет, а для выхода карту нужно вставить в специальный приёмник. Сданные карты возвращаются в дальнейший оборот.
Посмотрите, какие они милые!
3) Навигация в метро очень простая, т.к. каждая ветка имеет свой цвет и номер, а названия всех станций дублируются на английском. При этом в метро полно различных схем и указателей интуитивно понятных даже ребенку.
4) Реклама в метро - это то, что вас может напугать, если вы увидели это в первый раз. И это именно то, над чем вы будете размышлять несколько дней после этого. Но чем объяснять, проще показать (см. с 30 секунды):
Такое устройство называется "зоотроп" - система для демонстрации движущихся рисунков, конструкция которого основана на персистенции, то есть инерции человеческого зрения.
Светодиодные экраны размещаются один за другим на длинной панели, которая простирается от начала до конца туннеля. Но это не видео, а отдельные изображения.
Когда вы находитесь в поезде, и вы видите, что эти изображения загораются одно за другим, это выглядит как видео: настоящее 15-секундное коммерческое сообщение.
5) Машинист в шанхайском метро сам по себе не управляет поездом: все операции выполняются в автоматическом режиме. Его задача — следить за обстановкой в салоне и на перегоне, а также при необходимости активировать экстренное торможение в случае нештатной ситуации. При этом у машиниста нет отдельной кабины — его рабочее место расположено в голове первого вагона и открыто для пассажиров
6) Часы пик в шанхайском метро отличаются резкой неравномерностью пассажиропотока — в этом оно контрастирует с московским. Ведь, как мы все знаем, Moscow - never sleep: даже в час ночи у нас в метро довольно людно. В Шанхае же периоды полной загрузки резко сменяются почти полным опустением. С 9 до 10 утра и с 18 до 19 вечера в вагонах не протолкнуться, но уже к 20:00 город будто замирает — станции и поезда пустеют. Мы как раз в это время ехали из аэропорта (наш трансфер сгорел вместе с пропущенным рейсом) и были поражены полнейшим безлюдием в метро.
Кстати, на последнем фото - стеклянная перегородка отделяющая путь от платформы. Она оберегает пассажиров от случайного падения на пути как раз в часы пик, когда шанс быть выдавленным с платформы весьма не иллюзорный. Двери таких защитных перегородок идеально совпадают с дверями поезда и открываются синхронно.
На этом, пожалуй все) Надеюсь вам было интересно
Хотела нарисовать весёлый реверсивный след... Верите, искренне старалась, други мои. Ну хоть так.
Всем спокойного выходного вечера!
Всех продержавшихся - с преодолением экватора. Мы большие молодцы!
А мои новые маркеры оставляют хороший след, надеюсь их теперь хватит до финиша :)
Работы за вчерашний день:
И моё... Бесконечность
В прошлый раз мы нарисовали наш танк и научили его передвигаться. Теперь необходимо научить его врезаться в стены/воду.
Для этого создаём сущность коллизии и после инициализации уровня храним в памяти все коллизии и в момент попытки двигаться проверяем столкновение коллизий.
Это некая базовая версия физического движка. Нужно понимать логику: то, что игрок видит на экране не имеет никакого значения. Физика просто работает и говорит можно ехать дальше или нет. В какой-то мере это почти тоже самое, что происходит, например, в Unity. Но там система сложнее и она работает скорее подобно тому, как у меня сделано в тетрисе:
Здесь же я просчёт сделал по принципу экстраполяции (С Юнити тоже есть такой вариант у коллизий), когда просчёт пересечений происходит как бы с взглядом в будущее, просчёт потенциального пересечение ещё до того, как оно произошло.
Давайте же посмотрим, как это выглядит.
Коллизия – это не класс как таковой, это просто набор общедоступных методов.
/**
* Получить список субтайлов, покрываемых прямоугольником
*
* @returns {Array<{tx: number, ty: number}>} Массив координат субтайлов
*/
export function getTilesUnderEntity(x, y, width, height) {
const left = Math.floor(x / TILE_SIZE);
const right = Math.floor((x + width - 1) / TILE_SIZE);
const top = Math.floor(y / TILE_SIZE);
const bottom = Math.floor((y + height - 1) / TILE_SIZE);
const tiles = [];
for (let ty = top; ty <= bottom; ty++) {
for (let tx = left; tx <= right; tx++) {
// Проверяем границы сетки
if (tx >= 0 && tx < GRID_SIZE && ty >= 0 && ty < GRID_SIZE) {
tiles.push({ tx, ty });
}
}
}
return tiles;
}
/**
* Проверить, может ли танк войти на данный тайл
*
* @returns {boolean} true если танк может войти
*/
export function canTankEnter(tileDef, tank) {
if (!tileDef) return true;
// Вода: проходима только с бонусом "лодка"
if (tileDef.id === TileType.WATER) {
return tank && tank.hasBoat;
}
return !tileDef.blocksTank;
}
/**
* Проверить коллизию танка с картой при движении в новую позицию
* та самая экстраполяция
*
* @returns {boolean} true если коллизия есть (нельзя двигаться)
*/
export function checkTankMapCollision(tank, newX, newY, gameMap) {
// Получаем субтайлы под новой позицией танка
const tiles = getTilesUnderEntity(newX, newY, TANK_SIZE, TANK_SIZE);
// Проверяем каждый субтайл
for (const { tx, ty } of tiles) {
const tileId = gameMap.getTile(tx, ty);
const tileDef = TILE_DEFS[tileId];
if (!canTankEnter(tileDef, tank)) {
return true; // Есть пересечение
}
}
return false; // Нет пересечений
}
/**
* Проверить коллизию двух AABB (Axis-Aligned Bounding Box)
*
* @returns {boolean} true если боксы пересекаются
*/
export function checkAABBCollision(a, b) {
return (
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
);
}
/**
* Проверить коллизию двух танков
*
* @returns {boolean} true если есть коллизия с другим танком
*/
export function checkTankTankCollision(tank, newX, newY, tanks) {
const tankBox = {
x: newX,
y: newY,
width: TANK_SIZE,
height: TANK_SIZE
};
for (const other of tanks) {
// Пропускаем себя и уничтоженных
if (other === tank || other.destroyed) continue;
const otherBox = other.getBounds();
if (checkAABBCollision(tankBox, otherBox)) {
return true; // Коллизия с другим танком
}
}
return false;
}
А ещё с прошлого поста был проведён рефакторинг. У меня чесалось нёбо от неудобных некрасивых констант, с которыми были вездесущие сравнения. В целом, не магические числа – уже хорошо, но тем не менее. Енамки JS не поддерживает, поэтому делаем костыль через замороженные объекты.
// Было
export const TILE_EMPTY = 0;
export const TILE_BRICK = 1;
export const TILE_STEEL = 2;
export const TILE_WATER = 3;
export const TILE_FOREST = 4;
export const TILE_ICE = 5;
export const TILE_BASE = 6;
// Стало
export const TileType = Object.freeze({
EMPTY: 0,
BRICK: 1,
STEEL: 2,
WATER: 3,
FOREST: 4,
ICE: 5,
BASE: 6
});
Проверка самих коллизий происходит в методе передвижения танка
newX = Math.max(0, Math.min(newX, LOGICAL_FIELD_SIZE - TANK_SIZE));
newY = Math.max(0, Math.min(newY, LOGICAL_FIELD_SIZE - TANK_SIZE));
if (gameMap && checkTankMapCollision(this, newX, newY, gameMap)) {
return; // не применяем результат передвижения, просто выходим
}
Ну и в сам метод передаётся информация о карте в инпут менеджере
if (gp.buttons[12]?.pressed) player.move(Direction.UP, gameMap);
if (gp.buttons[13]?.pressed) player.move(Direction.DOWN, gameMap);
if (gp.buttons[14]?.pressed) player.move(Direction.LEFT, gameMap);
if (gp.buttons[15]?.pressed) player.move(Direction.RIGHT, gameMap);
А сам gameMap у нас и главного скрипта, а там он формируется как и раньше в
loadLevel(TEST_LEVEL_1);
Результат:
Далее по плану научить танк стрелять и, в идеале, отрабатывать попадания снарядов в стены.
@talk.about@Hippopofox@SergPrg@Linda_M@moortnelis@Aid314@Tiamin@Nataalika@Moonshine@Alenari@SergeyRY@Pepels@BespiriL@Cubinec@Palebody@DoctorDoom@rammdarkfunny@AlNiKo@kimpokom@Brainy@etoshtrudel@jewell...
Пизда Лепсу?
Это нужно как-то закрепить, зазернить в лисоленте