Первая статья И. BASH'им в начало
Вдохновившись отзывами на первую статью я продолжил разработку piu-piu. В игре появилось интро\меню, реализовано посимвольное появление объектов, изменилось цветовое решение. Палитра теперь определяется по времени года, правда, из-за недостатка цветов пришлось ограничиться 3-мя вариантами: зима — начало весны, весна — лето и осень. Изменения можно оценить, скачав игру тут. Далее немного букв как это все получилось.
Я начал с реализации посимвольного вывода объектов. Что может быть проще? Просто сделай срез элемента спрайта, но…
Управляющие коды (цвет) срежутся, и вместо цветного символа получится каша. Необходимо пересобрать элемент спрайта посимвольно, вставляя нужные управляющие коды перед каждым символом. Поприсядав изрядное количество времени со срезами я, наконец, разработал похожий алгоритм. Тестовый сценарий:
Получилось так:
Но этот алгоритм позволяет «красить» только строки целиком, это не было пределом мечтаний и не соответствовало существующей раскраске спрайтов. Пришлось поприседать еще. Таблица цветов расширилась, для каждого символа определен свой цвет. Каждый элемент массива цветов преобразуется в отдельный массив, индексы которого совпадают с индексами элемента спрайта. Затем элемент цвета и элемент спрайта рисуются в нужном месте и в нужное время.
Теперь все символы могут быть разноцветными:
Отлично! Я решил проверить как это будет рисоваться поверх другого спрайта. Добавил статический спрайт в основной цикл:
И проверил:
Нормально, но если сзади «маска» получается сама собой, то передняя часть рисуется квадратом, затирая все, опять присядания.
Красота!
Итак, спрайты режутся, скорей впихивай этот алгоритм в игру! Я попробовал и о… очень сильно расстроился. Скорость, мягко говоря, оставляла желать лучшего:
Множественные вложенные циклы отрицательно сказываются на быстродействии (внезапно). А ведь я пробовал без деревьев, облаков и прочей мишуры, мда.
Но после такого количества присяданий не хотелось выбрасывать идею на помойку. Моск начал работать. Решение было найдено. Использовать оба метода! Юху! Появление\исчезновение посимвольно а полет по экрану «быстрым» методом. Цикл объектов теперь выглядит так:
А вот функция mover:
Пришлось нарисовать по 2 комплекта спрайтов для обоих методов вывода. И ограничить количество одновременно вылетающих объектов. Для чужих я добавил в условие появления тайминг. А деревьям\облакам уменьшил вероятность появления. Вот какие спрайты получились, на примере дерева:
И тут мы плавно переходим к следующей фиче. Основной цвет деревьев, облаков и фона меняется в зависимости от времени года. Время года определяется по месяцу. Месяц определяется date'ом:
В разное время года игра будет выглядеть по разному. Сейчас так, осень:
А скоро будет так, зима — начало весны:
А так будет совсем не скоро. Весна — лето:
Но читеры могут сделать так:
Да, пулялка может увеличиваться до х5, и босс немного подрос.
А вот такое интро\меню появилось в игре:
Но тут тоже не все было гладко в плане быстродействия. Большие спрайты даже поодиночке жутко тормозили в посимвольном режиме вывода. Пришлось делать присядания опять, да.
Я решил нарезать спрайты на куски по 3 символа и выводить их «быстрым» методом:
Рисуется вся эта красота своими функциями left, right и intro:
Последовательность появления объектов задается в массиве scenario. Пункт меню conf дает возможность кастомизации (небольшой) самолетика. Можно изменить цвет самолетика и символ на хвосте (и его цвет).
И еще кучка мелких доработок\исправлений по ходу написания двух статей (к слову о пользе статей). Дальше хочется поработать над оптимизацией, приподнять фпс и попробовать реализовать кооператив по сети, продолжение следует. На этом пока все (где-то я это слышал).
Продолжение И. BASH'им вместе
Пиу, пиу, пиу! :)
Вдохновившись отзывами на первую статью я продолжил разработку piu-piu. В игре появилось интро\меню, реализовано посимвольное появление объектов, изменилось цветовое решение. Палитра теперь определяется по времени года, правда, из-за недостатка цветов пришлось ограничиться 3-мя вариантами: зима — начало весны, весна — лето и осень. Изменения можно оценить, скачав игру тут. Далее немного букв как это все получилось.
Гифка с новым геймплеем
Я начал с реализации посимвольного вывода объектов. Что может быть проще? Просто сделай срез элемента спрайта, но…
Управляющие коды (цвет) срежутся, и вместо цветного символа получится каша. Необходимо пересобрать элемент спрайта посимвольно, вставляя нужные управляющие коды перед каждым символом. Поприсядав изрядное количество времени со срезами я, наконец, разработал похожий алгоритм. Тестовый сценарий:
#!/bin/bash
# подключаю свою табличку с красками
. ~/SCR/color
# тестовый спрайт
sprite=(
'/¯¯¯¯¯\'
'| 0 |'
'\_____/'
)
# расцветка тестового спрайта
sprite_color=(
"$RED"
"$GRN"
"$BLU"
)
# уже знакомые функции выхода и определения размеров
function bye () {
stty echo
printf "${CON}${DEF}"
clear
ls --color=auto
exit
}
function get_dimensions {
size=($(stty size))
endx=${size[1]}
endy=${size[0]}
}
# инициализация
get_dimensions
X=$endx
Y=$[$endy/2]
sn=${#sprite[@]} # количество элементов спрайта
sl=${#sprite[0]} # ширина спрайта, считаетсяя по наибольшему элементу
trap bye INT; stty -echo; printf "${COF}"; clear
# цикл
while true; do sleep 0.1; get_dimensions
for (( p=0; p<${sn}; p++ )); do # цикл по элементам спрайта
end=$[1-(${X}-${endx})]
if [ $X -gt 0 ]; then
bgn=0
XY ${X} $(($Y + $p)) \
# цвет | срез спрайта
"${sprite_color[$p]}${sprite[$p]:${bgn}:${end}} ${DEF}"
else
bgn=$[1-$X]
XY 1 $(($Y + $p)) \
"${sprite_color[$p]}${sprite[$p]:${bgn}:${end}} ${DEF}"
fi
done
((X--)); [ $X -lt -${sl} ] && X=$endx
done
Получилось так:
Но этот алгоритм позволяет «красить» только строки целиком, это не было пределом мечтаний и не соответствовало существующей раскраске спрайтов. Пришлось поприседать еще. Таблица цветов расширилась, для каждого символа определен свой цвет. Каждый элемент массива цветов преобразуется в отдельный массив, индексы которого совпадают с индексами элемента спрайта. Затем элемент цвета и элемент спрайта рисуются в нужном месте и в нужное время.
#!/bin/bash
. ~/SCR/color
# новый тестовый спрайт
sprite=(
' /¯¯¯¯¯\ '
'-----|12345|-------- '
' --|12345|--- '
' ----|12345|---- '
' \_____/ '
)
# расцветка тестового спрайта, свой цвет для каждого символа
C01=$UND$BLU; C02=$UND$BLD$BLU C03=$DEF$MGN
sprite_color=(
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF"
"$DEF $RED $GRN $C01 $C03 $YLW $DEF $RED $DEF $DEF $DEF $BLU $DEF"
"$DEF $RED $GRN $C02 $C03 $YLW $DEF $DEF $GRN $DEF $DEF $DEF $DEF"
"$DEF $RED $GRN $C01 $C03 $YLW $DEF $DEF $DEF $BLU $DEF $DEF $DEF"
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF"
)
function bye () {
stty echo
printf "${CON}${DEF}"
clear
ls --color=auto
exit
}
function get_dimensions {
size=($(stty size))
endx=${size[1]}
endy=${size[0]}
}
get_dimensions
X=$endx
Y=$[$endy/2]
L=1
sn=${#sprite[@]}
sl=${#sprite[1]}
trap bye INT; stty -echo; printf "${COF}"; clear
while true; do sleep 0.1; get_dimensions
((L++)); ((X--)); [ $X -lt -${sl} ] && { X=$endx; L=1; }
for (( p=0; p<${sn}; p++ )); do
color=(${sprite_color[p]}); YY=$[$Y+${p}]
# вложенные циклы по символам элементов спрайта
if [ $X -gt 1 ]; then
for (( k=0; k<${L}; k++ )); do
XY $[${k}+${X}] ${YY} \
"${color[$k]}${sprite[$p]:$k:1}"
done
else
for (( k=1; k<${sl}; k++ )); do
XY ${k} ${YY} \
"${color[$[$k-$X]]}${sprite[$p]:$[$k-$X]}"
done
fi
done
done
Теперь все символы могут быть разноцветными:
Отлично! Я решил проверить как это будет рисоваться поверх другого спрайта. Добавил статический спрайт в основной цикл:
for (( p=0; p<${sn}; p++ )); do
XY 3 $[${endy}/2+${p}] "${sprite[$p]}"
done
И проверил:
Нормально, но если сзади «маска» получается сама собой, то передняя часть рисуется квадратом, затирая все, опять присядания.
#!/bin/bash
. ~/SCR/color
# у спрайта появились индексы смещения
sprite=(
5' /¯¯¯¯¯\ '
0' -----|12345|-------- '
3' --|12345|--- '
1' ----|12345|---- '
5' \_____/ '
)
# немного изменил таблицу расцветки
sprite_color=(
"$DEF"
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF $MGN $YLW $grn $RED $MGN $DEF"
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF $BLU $GRN $cyn $ylw $blu $DEF"
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF $YLW $RED $BLU $grn $YLW $DEF"
"$DEF"
)
function bye {
stty echo
printf "${CON}${DEF}"
clear
ls --color=auto
exit
}
function get_dimensions {
size=($(stty size))
endx=${size[1]}
endy=${size[0]}
}
get_dimensions
X=$endx
Y=$[$endy/2]
L=1
sn=${#sprite[@]}
sl=${#sprite[1]}
trap bye INT; stty -echo; printf "${COF}"; clear
while true; do sleep 0.1; get_dimensions
# фоновый спрайт
for (( p=0; p<${sn}; p++ )); do
XY 3 $[${endy}/2+${p}] "${sprite[$p]}"
done
# увеличиваю циферки
((L++)); ((X--))
[ $X -lt -${sl} ] && { X=$endx; L=1; }
for (( p=0; p<${sn}; p++ )); do
# определяю цвет
color=(${sprite_color[p]})
# координату Y
YY=$[$Y+${p}]
# смещение начала
stp=${sprite[$p]:0:1}
# срез спрайта
spr=${sprite[$p]:1}
if [ $X -gt 1 ]; then
for (( k=0; k<${L}; k++ )); do
if [ $k -ge $stp ]; then
XY $[${k}+${X}] ${YY} \
"${DEF}${color[$k]}${spr:$k:1}"
fi
done
else
for (( k=1; k<${sl}; k++ )); do
XY ${k} ${YY} \
"${color[$[$k-$X]]}${spr:$[$k-$X]}"
done
fi
done
done
Красота!
Небольшое лирическое отступление
Механика посимвольного вывода построена на срезах. Срезы вообще очень удобны. Их можно использовать для всяких прикольных штук при работе с текстом.
Баш позволяет делать срезы как переменных, так и массивов. Пример, срез переменной:
А теперь срез массива:
Помню, во времена Спектрума ты не мог называться крутым кодером, если не сделал крутой скроллер. И все крутые парни делали их(мы тоже). Во всех демках были скроллеры с бесконечными гритингсами, матами, шутками-прибаутками. Хорошее было время. Попробуем запилить скроллер на BASH'е, используя срезы конечно:
Привет мир!
А если прикрутить сюда figlet, получится вообще хорошо:
Тут есть одна тонкость. Я переопределяю символ «разделитель». По умолчанию разделителями являются: пробел, таб, переход строки. Все это хранится в системной переменной IFS. Я запоминаю старое значение:
Затем устанавливю разделителем только переход строки \n:
Офиглеваю текст в массив figlet_text командой:
Ключ -w задает фиглету ширину строки, т.к. по умолчанию он вписывает текст в экран. А это добавляет ненужные переходы строки, и чуда не происходит. Ну и возвращаю старое значение разделителя:
Фиглетовый скроллер!
Баш позволяет делать срезы как переменных, так и массивов. Пример, срез переменной:
a=1234567890
# отрезали 3 символа вначале
echo ${a:3}
4567890
# вырезали 3 символа из тела
echo ${a:3:3}
456
# можно указывать отрицательное значение второго элемента среза, это подрежет конец
echo ${a:(-4)}
123456
echo ${a:(-5):(-2)}
678
А теперь срез массива:
a=( 1 2 3 4 5 6 7 8 9 0 )
# отрезали 3 элемента вначале
echo ${a[*]:3}
4 5 6 7 8 9 0
# вырезали 3 символа из тела
echo ${a[*]:3:3}
# срез с конца
echo ${a[@]:(-5)}
6 7 8 9 0
# а так почему-то нельзя
echo ${a[@]:(-5):(-1)}
bash: (-1): выражение подстроки < 0
Помню, во времена Спектрума ты не мог называться крутым кодером, если не сделал крутой скроллер. И все крутые парни делали их(мы тоже). Во всех демках были скроллеры с бесконечными гритингсами, матами, шутками-прибаутками. Хорошее было время. Попробуем запилить скроллер на BASH'е, используя срезы конечно:
#!/bin/bash
# подключаю палитру и функцию рисования XY
. ~/SCR/color
# текст, можно задать параметром
text=${1:-'Hello Wo00000o0000000o000orld! '}; N=${#text}
# опять эти функции
function bye {
stty echo
printf "${CON}${DEF}"
clear
ls --color=auto
exit
}
function get_dimensions {
size=($(stty size))
endx=${size[1]}
endy=${size[0]}
}
# инициализация
get_dimensions
trap bye INT
stty -echo
printf "${COF}"
X=$[$endx+1]
Y=$[$endy/2]
L=0; clear
# цикл
while true; do sleep 0.05; get_dimensions
[ $X -gt 1 ] \
&& XY $X $Y "${text:0:$L}" \
|| XY 1 $Y "${text:$[1-$X]:$L}"
[ $X -lt -$N ] && { X=$endx; L=0; } || ((X--))
[ $L -lt $endx ] && ((L++))
done
Привет мир!
А если прикрутить сюда figlet, получится вообще хорошо:
#!/bin/bash
. ~/SCR/color
text=${1:-'Hello Wo00000o0000000o000orld! '}; N=${#text}
function bye {
stty echo
printf "${CON}${DEF}"
clear
ls --color=auto
exit
}
function get_dimensions {
size=($(stty size))
endx=${size[1]}
endy=${size[0]}
}
get_dimensions
trap bye INT
stty -echo
printf "${COF}"
IFSOLD=$IFS
IFS=$'\n'
# добавился фиглет
figlet_text=( $(figlet -w$[$N*10] "${text}") )
IFS=$IFSOLD
NF=${#figlet_text[1]}
NFL=${#figlet_text[*]}
X=$[$endx+1]
Y=$[$endy/2-$NFL/2]
L=0; clear
while true; do sleep 0.05; get_dimensions
if [ $X -gt 1 ]; then
# цикл по элементам фиглетового текста
for ((i=0; i<$NFL; i++)); do
XY $X $[$Y+$i] "${figlet_text[$i]:0:$L}"
done
else
for ((i=0; i<$NFL; i++)); do
XY 1 $[$Y+$i] "${figlet_text[$i]:$[1-$X]:$L}"
done
fi
[ $X -lt -$NF ] && { X=$endx; L=0; } || ((X--))
[ $L -lt $endx ] && ((L++))
done
Тут есть одна тонкость. Я переопределяю символ «разделитель». По умолчанию разделителями являются: пробел, таб, переход строки. Все это хранится в системной переменной IFS. Я запоминаю старое значение:
IFSOLD=$IFS
Затем устанавливю разделителем только переход строки \n:
IFS=$'\n'
Офиглеваю текст в массив figlet_text командой:
figlet_text=( $(figlet -w$[$N*10] "${text}") )
Ключ -w задает фиглету ширину строки, т.к. по умолчанию он вписывает текст в экран. А это добавляет ненужные переходы строки, и чуда не происходит. Ну и возвращаю старое значение разделителя:
IFS=$IFSOLD
Фиглетовый скроллер!
Итак, спрайты режутся, скорей впихивай этот алгоритм в игру! Я попробовал и о… очень сильно расстроился. Скорость, мягко говоря, оставляла желать лучшего:
Множественные вложенные циклы отрицательно сказываются на быстродействии (внезапно). А ведь я пробовал без деревьев, облаков и прочей мишуры, мда.
Но после такого количества присяданий не хотелось выбрасывать идею на помойку. Моск начал работать. Решение было найдено. Использовать оба метода! Юху! Появление\исчезновение посимвольно а полет по экрану «быстрым» методом. Цикл объектов теперь выглядит так:
NO=${#OBJ[@]}
for (( i=0; i<$NO; i++ )); do
OI=(${OBJ[$i]})
OX=${OI[0]}
OY=${OI[1]}
cuter=${OI[2]}
type=${OI[3]}
case $type in
# объекты с меняющимися спрайтами(быстрый\медленный)
# дерево 1 медленный спрайт
"tree1" ) sprite=("${tree1[@]}")
# палитра медленного спрайта
sprite_color=("${tree1_color[@]}")
# быстрый спрайт
sprite_fast=("${tree12[@]}")
# функция - двигатель
# +---------+------+------+------+
# | функция |таймер|высота|ширина|
# +---------+------+------+------+
mover $Q 4 4;;
# дерево 2
"tree2" ) sprite=("${tree2[@]}")
sprite_color=("${tree2_color[@]}")
sprite_fast=("${tree22[@]}")
mover $W 6 6;;
# дерево 3
"tree3" ) sprite=("${tree3[@]}")
sprite_color=("${tree3_color[@]}")
sprite_fast=("${tree32[@]}")
mover $E 9 10;;
# облако 1
"cloud1") sprite=("${cloud1[@]}")
sprite_color=("${cloud1_color[@]}")
sprite_fast=("${cloud12[@]}")
mover $Q 3 7;;
# облако 2
"cloud2") sprite=("${cloud2[@]}")
sprite_color=("${cloud2_color[@]}")
sprite_fast=("${cloud22[@]}")
mover $W 3 9;;
# облако 3
"cloud3") sprite=("${cloud3[@]}")
sprite_color=("${cloud3_color[@]}")
sprite_fast=("${cloud32[@]}")
mover $E 3 12;;
# враги
"alien" ) sprite=("${alien[@]}")
sprite_color=("${alien_color[@]}")
sprite_fast=("${alien2[@]}")
mover 0 3 5;;
# объекты с только быстрыми спрайтами
# выстрел босса
# быстрый спрайт
"bfire" ) sprite=("${bfire[@]}")
# функция - двигатель
# +---------+------+------+------+
# | функция |таймер|высота|ширина|
# +---------+------+------+------+
mover 0 6 4;;
# бонус - патроны
"ammo" ) sprite=("${ammob[@]}")
mover 0 3 4;;
# бонус - жизнь
"life" ) sprite=("${lifep[@]}")
mover 0 3 4;;
# бонус - усилитель ствола
"gunup" ) sprite=("${gunup[@]}")
mover 0 3 4;;
# взрывы, не латают, рисуем 1 раз
"boom" ) er=${boomC}
for part in "${boom[@]:$B:$boomC}"; do
stp=${part:0:1}
spr=${part:1}
XY $[${OX} + $stp] ${OY} "${spr}"; ((OY++))
done
[ ${E} = 0 ] && { ((B+=${boomC}))
[ $B -gt ${boomN} ] && { B=0; erase_obj ${i}; }; };;
esac; done
А вот функция mover:
function mover () {
er=$2 # кол-во линий спрайта
width=$3 # ширина спрайта
# плюсуем циферки
[ ${1} = 0 ] && {
((OX--))
((cuter++))
OBJ[$i]="$OX $OY $cuter $type"
}
# не улетел ли объект
case ${type} in
'alien'|'tree'[1-3]|'cloud'[1-3])
[ $OX -lt -$width ] && {
remove_obj ${i}
case ${type} in
"alien") ((enumber--));;
esac; return
};;
*)
[ $OX -lt 1 ] && {
erase_obj ${i}
case ${type} in
"alien") ((enumber--));;
esac; return; };;
esac
# рисовалка
for (( p=0; p<${er}; p++ )); do
case ${type} in
# быстрые\медленные спрайты
'alien'|'tree'[1-3]|'cloud'[1-3])
color=(${sprite_color[$p]})
YY=$[$OY+${p}]
stp=${sprite[$p]:0:1}
spr=${sprite[$p]:1}
# прилетает\летит
if [ $OX -gt 1 ]; then
if [ $cuter -lt $width ]; then
# прилетает, посимвольный вывод
for (( k=0; k<${cuter}; k++ )); do
if [ $k -ge $stp ]; then
XY $[$k+$OX] $YY \
"${color[$k]}${spr:$k:1}"
fi
done
else
# летит, переключение на быстрый спрайт
stp=${sprite_fast[$p]:0:1}
spr=${sprite_fast[$p]:1}
XY $[${OX} + $stp] $[$OY + $p] "${spr}"
fi
# улетает
else
# опять посимвольно
for (( k=1; k<${width}; k++ )); do
x=$[$k-$OX]
XY $k $YY "${color[$x]}${spr:$x}"
done
fi;;
# только быстрые спрайты
*) XY ${OX} $[$OY + $p] "${sprite[$p]}";;
esac
# проверка коллизий
case ${type} in
"gunup" )
case "$[$OY + $p] $OX" in
"$HY $HX")
[ ${G} -lt 4 ] && ((G++))
erase_obj ${i}
break;;
esac;;
"life" )
case "$[$OY + $p] $OX" in
"$HY $HX")
((life++))
erase_obj ${i}
break;;
esac;;
"ammo" )
case "$[$OY + $p] $OX" in
"$HY $HX")
((ammo+=100))
erase_obj ${i}
break;;
esac;;
"bfire" )
case "$OY $OX" in
"$HY $HX")
((life--))
erase_obj ${i}
break;;
esac;;
"alien" )
# столкновение с пулей
for (( t=0; t<${NP}; t++ )); do
case "$[$OY + 1] $[$OX + $p]" in
"${PIU[$t]}") # есть бонус?
if [ $[RANDOM % $rnd] -eq 0 ]; then
OBJ+=("$OX $OY 0 ${bonuses[$[RANDOM % \
${#bonuses[@]}]]}")
((frags++))
((enumber--))
remove_obj ${i}
remove_piu ${t}
OBJ+=("${OX} ${OY} 0 boom")
break
fi;;
esac
done
# столкновение с героем
case "$[$OY + 1] $[$OX + $p]" in
"$HY $HX")
((life--))
((frags++))
((enumber--))
remove_obj ${i}
OBJ+=("${OX} ${OY} 0 boom")
break;;
esac;;
esac
done
}
Пришлось нарисовать по 2 комплекта спрайтов для обоих методов вывода. И ограничить количество одновременно вылетающих объектов. Для чужих я добавил в условие появления тайминг. А деревьям\облакам уменьшил вероятность появления. Вот какие спрайты получились, на примере дерева:
# "медленный" спрайт
tree3=(
3' _._ '
2' / \ '
1' _\ | / '
0'/ \║/__ '
0'\_\/║/ \ '
3' \║|/_/ '
4' ║/ '
4' ║ '
4' ║ ')
# основной цвет меняется в зависимости от времени года
case $month in
0[1-4]|12) CLR=${cyn} ;; # зима
0[5-8] ) CLR=${BLD}${GRN};; # лето
09|1[0-1]) CLR=${DIM}${red};; # осень
esac
CM1=${SKY}${BLK}
tree3_color=(
"${SKY} ${SKY} ${SKY} ${CLR} ${CLR} ${CLR} ${SKY}"
"${SKY} ${SKY} ${CLR} ${SKY} ${SKY} ${SKY} ${CLR} ${SKY}"
"${SKY} ${CLR} ${CLR} ${SKY} ${CM1} ${SKY} ${CLR} ${SKY}"
"${CLR} ${SKY} ${SKY} ${CLR} ${CM1} ${CLR} ${CLR} ${CLR} ${SKY}"
"${CLR} ${CLR} ${CM1} ${CLR} ${CM1} ${CLR} ${SKY} ${SKY} ${CLR} ${SKY}"
"${SKY} ${SKY} ${SKY} ${CM1} ${CM1} ${CLR} ${CM1} ${CLR} ${CLR} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${CM1} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${SKY}")
# "быстрый" спрайт
tree32=(
3${CLR}'_._ '${SKY}
2${CLR}'/ \ '${SKY}
1${CLR}'_\ '${CM1}'|'${CLR}' / '${SKY}
0${CLR}'/ \\'${CM1}'║'${CLR}'/__ '${SKY}
0${CLR}'\_'${CM1}'\\'${CLR}'/'${CM1}'║'${CLR}'/ \ '${SKY}
3${BLK}'\║'${CLR}'|'${CM1}'/'${CLR}'_/ '${SKY}
4${BLK}'║/ '${SKY}
4${BLK}'║ '${SKY}
4${BLK}'║ '${SKY})
И тут мы плавно переходим к следующей фиче. Основной цвет деревьев, облаков и фона меняется в зависимости от времени года. Время года определяется по месяцу. Месяц определяется date'ом:
month=$(date +'%m')
В разное время года игра будет выглядеть по разному. Сейчас так, осень:
А скоро будет так, зима — начало весны:
А так будет совсем не скоро. Весна — лето:
Но читеры могут сделать так:
month=07
Да, пулялка может увеличиваться до х5, и босс немного подрос.
А вот такое интро\меню появилось в игре:
Но тут тоже не все было гладко в плане быстродействия. Большие спрайты даже поодиночке жутко тормозили в посимвольном режиме вывода. Пришлось делать присядания опять, да.
Я решил нарезать спрайты на куски по 3 символа и выводить их «быстрым» методом:
D=$DEF; C1=$BLU; C2=$RED; C3=$YLW; C4=$red
C5=$BLD$YLW; C6=$BLD$GRN; C7=$blu; C8=$BLD$RED
# ░ ░░░ ░░ ░ ░░ ░░ ░░ ░
# ░ ░ ▒██████ ░▒██████ ▒██░ ▒██
# ░ ░░▒▓██ ▒▓██ ▓▓▓██▓ ▒▓██ ░▒▓██░░ ░
#░ ░ ░░▒▓██ ▒▓██ ▒▒▒▓██ ░▒▓██░ ▒▓██░
# ▒▓██████░ ░▒▓██ ░▒▓██ ░▒▓██░░ ░ ░
# ░ ▒▓██▓▓▓▓ ░ ▒▓██░ ▒▓██░░▒▓██ ░
# ▒▓████▒▒▒░ ░ ▓██████ ▒▓██████░░ ░
#░ ░▒▓▓▓▓ ░ ░░▒▓▓▓▓▓▓ ░ ▒▓▓▓▓▓░
# ░ ▒▒ ░ ░ ░ ░▒▒▒▒▒▒ ░ ░░▒▒▒▒░ ░
piu=(
"$C1 " "░ " " ░░" "░ ░" "░ " " " "░ " "░░ " " " " ░" "░ " "░░ " "░$D " " "
"$C1 ░ " " ░" " $C2▒" "$C3███" "███" " $C1░$C2▒" "$C3███" "███" " $C2▒$C3█" "█$C1░ " " $C2▒$C3█" "█$D " " " " "
"$C1 " "░ ░" "░$C2▒$C4▓" "$C3██ " "$C2▒$C4▓$C3█" "█ $C4▓" "▓▓$C3█" "█$C4▓ " "$C1▒$C4▓$C3█" "█ $C1░" "$C2▒$C4▓$C3█" "█$C1░░" " ░$D " " "
"$C1░ ░" " ░░" "$C2▒$C4▓$C3█" "█ $C2▒" "$C4▓$C3██" " $C2▒▒" "▒$C4▓$C3█" "█ $C2░" "▒$C4▓$C3█" "█$C1░ " "$C2▒$C4▓$C3█" "█$C1░$D " " " " "
"$C1 " " " "$C2▒$C4▓$C5█" "███" "██$C1░" " ░$C2▒" "$C4▓$C3██" " $C1░$C2▒" "$C4▓$C3██" " $C1░$C2▒" "$C4▓$C3██" "$C1░░ " "░ ░$D" " "
"$C1 ░" " $C2▒" "$C4▓$C3██" "$C4▓▓▓" "▓ " "$C1░ $C2▒" "$C4▓$C3██" "$C1░ $C2▒" "$C4▓$C3██" "$C1░░$C2▒" "$C4▓$C3██" " $C1░$D " " " " "
"$C2 " "▒$C4▓$C3█" "███" "$C2▒▒▒" "$C1░ ░" " $C4▓$C3█" "███" "██ " "$C1▒$C4▓$C3█" "███" "██$C1░" "░ ░$D" " " " "
"$C1░ ░" "$C2▒$C4▓▓" "▓▓ " "$C1░ ░" "░$C2▒$C4▓" "▓▓▓" "▓▓ " "$C1░ $C2▒" "$C4▓▓▓" "▓▓$C1░$D" " " " " " " " "
"$C1 ░ " "▒▒ " "░ ░" " ░ " "░▒▒" "▒▒▒" "▒ ░" " ░░" "▒▒▒" "▒░ " " ░ " " " " " "$D ")
piuN=${#piu[*]}; piuC=14
# ░░ ▒▒███░
# ░░░ ▒████ ▒▓███░
# ░ ▒████▒▓▓▓▓ ░░▒▓▓███ ░░ ░
#░ ▒▓▓▓▓ ▒████░▒▓███░░
# ▒ ▒▒ ▒▓▓▓▓▒▓███░
# ░░░ ▒▒▒ ░░▒▒░
arr=(
" " " " " " " " " $C7░░" " $C3▒▒" "$C6███" "$C7░$D " "" ""
" " " " " $C7░" "░░ " "$C2▒$C8██" "██ " "$C7▒$C3▓$C6█" "██$C7░$D" "" ""
" " " $C7░ " "▒$C8██" "██$D$C2▒" "$C3▓▓▓" "▓ $C7░" "░$C7▒$C3▓" "▓$C6██" "█ $C7░" "░ ░$D"
" " "$C7░ $C2▒" "$C3▓▓▓" "▓ $C2▒" "$C8███" "█$D$C7░$C3▒" "$C3▓$C6██" "█$C7░░$D" " " ""
" " " $C7▒ " "▒▒ " " $C2▒$C3▓" "▓▓▓" "$C7▒$C3▓$C6█" "██$C7░$D" " " "" ""
" " " " " $C7░░" "░ ▒" "▒▒ " "░░▒" "▒░$D " " " "" "")
arrN=${#arr[*]}; arrC=10
Рисуется вся эта красота своими функциями left, right и intro:
function left () { N=$1; C=$2
# move
[ $OX -ge $end ] && {
((OX-=3)); [ $cuter -ne $C ] && ((cuter++))
for ((j=0; j<$N; j+=$C)); do
line=("${sprite[@]:$j:$cuter}")
YY=$[$OY+$j/$C]; spr=
for part in "${line[@]}"; dospr+="${part}"; done
XY $OX $YY "${spr}"
done; OBJ[$i]="$OX $OY $cuter $end $type $pause"
} || { remove_obj $i; ((Q++)); OBJ+=("${scenario[$Q]}"); }
}
function right () { N=$1; C=$2
# move
[ $OX -le $end ] && {
[ $cuter -ne $C ] && ((cuter++)) || ((OX+=3))
for ((j=0; j<$N; j+=$C)); do
line=("${sprite[@]:$j:$C}")
YY=$[$OY+$j/$C]; spr=
for ((k=$[$C-$cuter]; k<$C; k++)); do spr+="${line[k]}"; done
XY $OX $YY "${spr}"
done; OBJ[$i]="$OX $OY $cuter $end $type $pause"
} || { remove_obj $i; ((Q++)); OBJ+=("${scenario[$Q]}"); }
}
function intro { get_dimensions; Q=0
scenario=(
# сценарий появления объектов
#----------+-------+-----+------------+----+
# старт X |старт Y|резак| конец X |тип |
#----------+-------+-----+------------+----+
"$[$endx+1] 3 0 $[endx/2-34] piu"
"$[$endx+1] 3 0 $[endx/2+2] piu"
"-2 12 0 $[endx/2-16] arr"
"0 0 0 0 end")
OBJ=("${scenario[$Q]}")
while true; do sleep 0.005; NO=${#OBJ[@]}
for (( i=0; i<$NO; i++ )); do
OI=(${OBJ[$i]}); OX=${OI[0]}; OY=${OI[1]}
cuter=${OI[2]}; end=${OI[3]}; type=${OI[4]}
case $type in
#-----+-------------------+------+------+------+
#тип | спрайт |функц.|высота|ширина|
#-----+-------------------+------+------+------+
"arr") sprite=("${arr[@]}"); right $arrN $arrC;;
"piu") sprite=("${piu[@]}"); left $piuN $piuC;;
"end") return ;;
esac
done
done
}
Последовательность появления объектов задается в массиве scenario. Пункт меню conf дает возможность кастомизации (небольшой) самолетика. Можно изменить цвет самолетика и символ на хвосте (и его цвет).
И еще кучка мелких доработок\исправлений по ходу написания двух статей (к слову о пользе статей). Дальше хочется поработать над оптимизацией, приподнять фпс и попробовать реализовать кооператив по сети, продолжение следует. На этом пока все (где-то я это слышал).
Продолжение И. BASH'им вместе
Пиу, пиу, пиу! :)