Очитавање тастатуре¶
Када је потребно да проверимо да ли корисник држи неки од тастера на тастатури притиснут, користимо логичку функцију Keyboard.IsKeyDown (IsKeyDown је метода класе Keyboard). Да бисмо могли да користимо ову функцију без навођења пуног имена (квалификованог имена), потребно је да додамо следећу наредбу на почетак нашег програма:
using System.Windows.Input;
Тиме смо најавили да ћемо да користимо простор имена (namespace) у коме се налази и име ове функције, тако да је уместо System.Windows.Input.Keyboard.IsKeyDown довољно писати само Keyboard.IsKeyDown.
Функција Keyboard.IsKeyDown прихвата један параметар, који представља један тастер на тастатури. Функција враћа true ако је тастер притиснут, а false ако није. За сваки тастер постоји именована константа (у именском простору System.Windows.Input) која га представља. На пример, константе Key.Left, Key.Right, Key.Up, Key.Down представљају тастере са стрелицама лево, десно, горе и доле редом. У примерима који следе најчешће ћемо користити управо ове константе. Неке од осталих константи које представљају тастере тастатуре су:
Key.Space представља размак
Key.PageUp, Key.PageDown, Key.Home, Key.End, Key.Insert, Key.Delete представљају тастере који се тако и зову
Key.A, Key.B, Key.C, … Key.Z представљају тастере са словима
Key.D0, Key.D1, Key.D2, … Key.D9 представљају тастере 0-9 у реду тастатуре изнад слова
Key.NumPad0, Key.NumPad1, Key.NumPad2, … Key.NumPad9 представљају тастере 0-9 на нумеричкој тастатури (десно)
Key.F1, Key.2, Key.3, … Key.12 представљају функцијске тастере у највишем реду тастатуре
Комплетан списак ових константи можете да видите на пример овде .
У наредним примерима ћемо (поред осталог) показати како се користи функција Keyboard.IsKeyDown и ове константе.
Свемирски брод¶
Сцена је описана положајем свемирског брода (целобројне променљиве RaketaX, RaketaY) и положајима испаљених метака (листа тачака IspaljeniMeci).
У функцији timer1_Tick као и обично, ажурирамо сцену:
Ако је притиснута лева стрелица померамо брод два пиксела лево, али не ван прозора.
Слично радимо и ако је ако је притиснута десна стрелица.
Ако је притиснут размак, на листу IspaljeniMeci додајемо нову тачку, положај управо испаљеног метка (координате те тачке су једнаке врху брода)
Осим ових реакција на притиснуте тастере, треба још померити навише све раније испаљене метке који се и даље виде. Тачке које имају премалу Y координату желимо да избацимо из листе. Једноставан и ефикасан начин (у смислу брзине извршавања) да ажурирамо листу положаја метака је да направимо нову листу, у коју ћемо ставити поправљене положаје смо оних метака који су и даље у прозору. На крају обраде свих метака, старој листи додељујемо нову (ово је брза операција, јер не долази до „преписивања” тачака).
Након ажурирања позиција, у функцији Form1_Paint само цртамо слику брода и за сваки метак по један мали круг.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Input;
namespace Navigacija
{
public partial class Form1 : Form
{
Image Raketa = Properties.Resources.SvemirskiBrod;
int RaketaX, RaketaY; // polozaj rakete
List<PointF> IspaljeniMeci;
const float BrzinaMetka = 5.0f;
public Form1()
{
InitializeComponent();
ClientSize = new Size(800, 400);
Text = "Raketa";
BackColor = Color.Black;
RaketaX = ClientSize.Width / 2;
RaketaY = ClientSize.Height - Raketa.Height;
IspaljeniMeci = new List<PointF>();
}
private void timer1_Tick(object sender, EventArgs e)
{
// Pomeranje rakete levo-desno i pucanje
if (Keyboard.IsKeyDown(Key.Left) && RaketaX > 0) RaketaX -= 2;
if (Keyboard.IsKeyDown(Key.Right) && RaketaX < ClientSize.Width - Raketa.Width) RaketaX += 2;
if (Keyboard.IsKeyDown(Key.Space))
IspaljeniMeci.Add(new PointF() { X = RaketaX + Raketa.Width/2, Y = RaketaY });
// Pomeranje metaka
List<PointF> NoviMeci = new List<PointF>();
foreach (var metak in IspaljeniMeci)
{
float y1 = metak.Y - BrzinaMetka;
if (y1 >= 0)
NoviMeci.Add(new PointF() { X = metak.X, Y = y1 });
}
IspaljeniMeci = NoviMeci;
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(Raketa, RaketaX, RaketaY);
// Nacrtaj metke
Brush cetka = new SolidBrush(Color.White);
const int R = 2; // poluprecnik metka
foreach (var metak in IspaljeniMeci)
g.FillEllipse(cetka, metak.X - R, metak.Y - R, 2 * R, 2 * R);
}
}
}
У многим програмима се управља неким објектом на екрану помоћу стрелица на тастатури. При томе, управљање може да се оствари на различите начине. У претходном примеру брод-ракета се равномерно креће док је одговарајући тастер притиснут, а зауставља се ако ни један тастер није притиснут. У следећем примеру ћемо видети да није тешко испрограмирати и другачији начин управљања објектом.
Лоптица¶
Написати програм, помоћу којег се управља жутом лоптицом на екрану, користећи стрелице на тастатури. Лоптица треба да убрзава док је одговарајући тастер притиснут, а да се креће равномерно када ни један тастер није притиснут. Када лоптица стигне до ивице прозора, треба да се појави са супротне стране.
У овом примеру за опис сцене се корсите четири броја - координате центра лоптице (X и Y) и вектор за који се лоптица помера у једном фрејму (DX и DY). Овај вектор можемо звати и вектор брзине лоптице.
Када у функцији timer1_Tick ажурирамо сцену, прво на основу стања тастера ажурирамо вектор брзине, а онда на основу вектора брзине ажурирамо положај лоптице. Пошто лоптица пролази кроз ивице прозора, то јест излази са једне а улази са друге стране прозора, природно је да све 4 величине рачунамо по модулу величине прозора. При томе водимо рачуна да DX и DY не постану негативни, па уместо да одузимамо 1, ми додајемо ClientSize.Width - 1 када је притиснута лева, а ClientSize.Height - 1 када је притиснута горња стрелица (што је при рачунању по модулу суштински исто).
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Input;
namespace Navigacija2
{
public partial class Form1 : Form
{
int X, Y, DX, DY; // polozaj i brzina kugle
const int R = 10; // poluprecnik kugle
public Form1()
{
InitializeComponent();
Text = "Navigacija";
BackColor = Color.Black;
X = ClientSize.Width / 2;
Y = ClientSize.Height / 2;
DX = 0;
DY = 0;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (Keyboard.IsKeyDown(Key.Left)) DX = (DX + ClientSize.Width - 1) % ClientSize.Width;
if (Keyboard.IsKeyDown(Key.Right)) DX = (DX + 1) % ClientSize.Width;
if (Keyboard.IsKeyDown(Key.Up)) DY = (DY + ClientSize.Height - 1) % ClientSize.Height;
if (Keyboard.IsKeyDown(Key.Down)) DY = (DY + 1) % ClientSize.Height;
if (DX != 0 || DY != 0)
{
X = (X + DX) % ClientSize.Width;
Y = (Y + DY) % ClientSize.Height;
Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush cetka = new SolidBrush(Color.Yellow);
g.FillEllipse(cetka, X - R, Y - R, 2 * R, 2 * R);
}
}
}
У следећем примеру ћемо показати још један чест начин управљања објектима на екрану помоћу стрелица.
Окретање¶
Написати програм у коме је свемирски број представљен малим троуглом. Бродом се управља на следећи начин:
Док је притиснута лева или десна стрелица, брод се окреће (ротира) у одговарајућем смеру (у смеру казаљке на сату за десну, а у супоротном смеру за леву).
Док је притиснута стрелица горе, брод убрзава у смеру и ком је окренут, али само до одређене максималне брзине.
Док је притиснута стрелица доле, брод успорава (односно убрзава ако се већ креће уназад, до исте максималне брзине).
Када стигне до ивице прозора, брод се зауставља.
Док је притиснут размак, испаљују се меци као у првом примеру ове лекције.
Објекти које пратимо су брод и испаљени меци.
Брод је описан положајем, смером у ком је окренут и вектором брзине (X, Y, Ugao, BrzinaX, BrzinaY). Све ове величине су реални бројеви у програму. Променљива Ugao је угао у радијанима између X осе и осе брода.
Сваки метак је описан положајем и вектором брзине (укупно четири броја). Потребно је да сваки метак има своју брзину, јер на брзину метка утиче брзина и оријентација брода у тренутку испаљивања. Листу метака ажурирамо на врло сличан начин као у првом примеру. Разлика је у томе што сваки метак има своју брзину и што може да изађе из прозора на било коју страну. Да бисмо податке о мецима могли да држимо у једној листи, уводимо једноставну класу Metak, која само садржи положај и вектор брзине метка (нема друге податке нити било какве методе).
class Metak { public float X, Y, DX, DY; };
Ажурирање сцене у функцији timer1_Tick је сада нешто сложеније:
ако је притиснута стрелица десно, угао повећавамо за 0.1 (брод ротира за 0.1 радијан, што је мало мање од 6 степени)
слично, ако је притиснута стрелица лево, угао смањујемо за 0.1
ако је притиснута стрелица горе, повећавамо брзину. То постижемо тако што променљиве BrzinaX и BrzinaY редом повећавамо за \(\cos(Ugao)\) и \(\sin(Ugao)\).
обрнуто, ако је притиснута стрелица доле, променљиве BrzinaX и BrzinaY смањујемо за исту вредност.
ако је брзина дуж било које осе изашла из дозвољеног опсега, враћамо је на најближу вредност у дозвољеном опсегу
ако је притиснут размак, листи метака додајемо један метак. Положај тог метка је врх брода, а брзина метка је једнака збиру брзине брода и вектора дужине 5 (пиксела), усмереног исто као и брод: \(DX = BrzinaX + 5 * \cos(Ugao), DY = BrzinaY + 5 * \sin(Ugao)\).
након обраде притиснутих тастера ажурирамо положај брода и метака (за метке поново користимо нову, привремену листу). У случају да је брод дошао до ивице прозора, његову брзину постављамо на нулу.
При цртању је најтежи део израчунавање темена троугла којим је представљен брод. Једно теме (врх брода) је тачка (X, Y) коју пратимо, а остала два темена израчунавамо на основу положаја (врха) брода и његове оријентације (променљива Ugao). Узели смо да је дужина брода (висина троугла) 20 пиксела, а ширина брода (основица троугла) 10 пиксела. Зато до остала два темена долазимо тако што се од врха брода померамо 20 пиксела уназад (у односу на оријентацију брода) и по пет пиксела лево и десно.
float dx = (float)Math.Cos(Ugao);
float dy = (float)Math.Sin(Ugao);
Marker[0] = new PointF(X, Y);
Marker[1] = new PointF(X - 20 * dx + 5 * dy, Y - 20 * dy - 5 * dx);
Marker[2] = new PointF(X - 20 * dx - 5 * dy, Y - 20 * dy + 5 * dx);
Надамо се да ћете моћи у потпуности да разумете код, да ће вам бити забавно да тестирате програм и да сте добили неке идеје за своје програме које ћете хтети да напишете и испробате.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Input;
namespace Navigacija3
{
public partial class Form1 : Form
{
class Metak { public float X, Y, DX, DY; };
float X, Y, Ugao, BrzinaX, BrzinaY;
PointF[] Marker;
const int R = 2; // poluprecnik metka
const float MaxBrzina = 5.0f;
List<Metak> IspaljeniMeci;
public Form1()
{
InitializeComponent();
Text = "Navigacija";
BackColor = Color.Black;
X = ClientSize.Width / 2;
Y = ClientSize.Height / 2;
Ugao = BrzinaX = BrzinaY = 0.0f;
Marker = new PointF[3];
IspaljeniMeci = new List<Metak>();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (Keyboard.IsKeyDown(Key.Left)) Ugao -= 0.1f;
if (Keyboard.IsKeyDown(Key.Right)) Ugao += 0.1f;
if (Keyboard.IsKeyDown(Key.Up))
{
BrzinaX += (float)Math.Cos(Ugao);
BrzinaY += (float)Math.Sin(Ugao);
}
if (Keyboard.IsKeyDown(Key.Down))
{
BrzinaX -= (float)Math.Cos(Ugao);
BrzinaY -= (float)Math.Sin(Ugao);
}
BrzinaX = Math.Min(MaxBrzina, Math.Max(-MaxBrzina, BrzinaX));
BrzinaY = Math.Min(MaxBrzina, Math.Max(-MaxBrzina, BrzinaY));
if (Keyboard.IsKeyDown(Key.Space))
IspaljeniMeci.Add(new Metak()
{
X = X,
Y = Y,
DX = BrzinaX + 5 * (float)Math.Cos(Ugao),
DY = BrzinaY + 5 * (float)Math.Sin(Ugao)
});
float x1 = X + BrzinaX;
float y1 = Y + BrzinaY;
if (x1 >= 0 && x1 < ClientSize.Width && y1 >= 0 && y1 < ClientSize.Height)
{
X = x1;
Y = y1;
}
else
{
BrzinaX = 0.0f;
BrzinaY = 0.0f;
}
List<Metak> NoviMeci = new List<Metak>();
foreach (var metak in IspaljeniMeci)
{
x1 = metak.X + metak.DX;
y1 = metak.Y + metak.DY;
if (x1 >= 0 && x1 < ClientSize.Width && y1 >= 0 && y1 < ClientSize.Height)
NoviMeci.Add(new Metak() { X = x1, Y = y1, DX = metak.DX, DY = metak.DY });
}
IspaljeniMeci = NoviMeci;
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush zutaCetka = new SolidBrush(Color.Yellow);
float dx = (float)Math.Cos(Ugao);
float dy = (float)Math.Sin(Ugao);
Marker[0] = new PointF(X, Y);
Marker[1] = new PointF(X - 20 * dx + 5 * dy, Y - 20 * dy - 5 * dx);
Marker[2] = new PointF(X - 20 * dx - 5 * dy, Y - 20 * dy + 5 * dx);
g.FillPolygon(zutaCetka, Marker);
Brush belaCetka = new SolidBrush(Color.White);
foreach (var metak in IspaljeniMeci)
g.FillEllipse(belaCetka, metak.X - R, metak.Y - R, 2* R, 2 * R);
}
}
}