Реализованы основные компоненты 2D игры: графика, обработка столкновений, управление с клавиатуры, анимированные спрайты, простейший AI, игровая механика.
Основные классы:
Коллайдер:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| /// <summary>
/// коллайдер
/// </summary>
class Collider
{
public void Update()
{
var items = Game.Instance.Items.ToList();
//проверка столкновения объектов
for (int i = 0; i < items.Count; i++)
for (int j = i + 1; j < items.Count; j++)
{
var p1 = items[i].Location;
var p2 = items[j].Location;
var d = p1.Distance(p2);
var overlap = items[i].Radius + items[j].Radius - d;
if (overlap > 0)
{
//столкновение
var dir = new PointF(p1.X - p2.X, p1.Y - p2.Y);
var l = dir.Distance(PointF.Empty);
if (items[i].Mass > items[j].Mass)
items[j].Location = new PointF(p2.X - dir.X * overlap / l, p2.Y - dir.Y * overlap / l);//отскок
else
items[i].Location = new PointF(p1.X + dir.X * overlap / l, p1.Y + dir.Y * overlap / l);//отскок
//
items[i].OnCoollide(items[j]);
items[j].OnCoollide(items[i]);
}
}
//проверка столкновения со стеной
for (int i = 0; i < items.Count; i++)
{
var p = items[i].Location;
if (p.X < 0) p.X = 0;
if (p.Y < 0) p.Y = 0;
if (p.X > Game.Instance.FieldSize.Width) p.X = Game.Instance.FieldSize.Width;
if (p.Y > Game.Instance.FieldSize.Height) p.Y = Game.Instance.FieldSize.Height;
if(p != items[i].Location)
{
//столкновение со стеной
items[i].Location = p;
items[i].OnCoollide(null);
}
}
}
} |
|
Базовый класс для игровых объектов:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| /// <summary>
/// базовый класс для игровых объектов
/// </summary>
[Serializable]
abstract class GameItem
{
public PointF Location { get; set;}
public int Radius { get; protected set; }
public virtual void Update(float dt){;}
public float Mass { get; set; }
public virtual void OnCoollide(GameItem otherItem){;}
public GameItem()
{
Game.Instance.Items.Add(this);
}
public void Remove()
{
Game.Instance.Items.Remove(this);
}
public virtual void Draw(Graphics gr)
{
if (Game.DebugMode)
gr.DrawEllipse(Pens.Red, -Radius, -Radius, 2 * Radius, 2 * Radius);
}
} |
|
Танк:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
| /// <summary>
/// танк
/// </summary>
[Serializable]
class Tank : GameItem
{
public PointF Velocity { get; set; }
public PointF TowerDirection { get; set; }
public virtual float MaxSpeed { get; protected set; }
public bool IsDead { get; set; }
public Tank()
{
Location = Game.Instance.GetRndPoint();
Radius = 25;
MaxSpeed = 5f;
Mass = 50;
}
public override void Update(float dt)
{
if(!IsDead)
Location = new PointF(Location.X + Velocity.X * dt, Location.Y + Velocity.Y * dt);
}
public override void Draw(Graphics gr)
{
//корпус
var img = Properties.Resources.TankBase;
gr.ResetTransform();
gr.TranslateTransform(Location.X, Location.Y);
gr.RotateTransform(180 + 180 * (float)(Math.Atan2(Velocity.Y, Velocity.X) / Math.PI));
gr.DrawImage(img, -50, -40);
//башня
img = Properties.Resources.TankTower;
gr.ResetTransform();
gr.TranslateTransform(Location.X, Location.Y);
gr.RotateTransform(180 + 180 * (float)(Math.Atan2(TowerDirection.Y, TowerDirection.X) / Math.PI));
gr.DrawImage(img, -50, -40);
//если горим
if(IsDead)
{
gr.TranslateTransform(-28, -28);
Game.FireSprite.Draw(gr);
}
base.Draw(gr);
}
[NonSerialized]
private DateTime lastFireTime;
//сделать выстрел
public void Fire()
{
if ((DateTime.Now - lastFireTime).TotalMilliseconds < 2500)
return;
if (IsDead)
return;
var speed = 13;
var l = TowerDirection.Distance(PointF.Empty);
var dir = new PointF(TowerDirection.X / l, TowerDirection.Y / l);
var v = new PointF(speed * dir.X, speed * dir.Y);
var bullet = new Bullet(){Velocity = v};
var pad = (Radius + bullet.Radius + 20);
bullet.Location = new PointF(Location.X + dir.X * pad, Location.Y + dir.Y * pad);
lastFireTime = DateTime.Now;
}
} |
|
Танк под управлением AI:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| /// <summary>
/// танк под управлением ИИ
/// </summary>
[Serializable]
class TankAI : Tank
{
private PointF targetPoint;
private float timeToChangeTargetPoint;
public override void Update(float dt)
{
timeToChangeTargetPoint -= dt;
if (!IsDead)
if (timeToChangeTargetPoint <= 0 || targetPoint.Distance(Location) < Radius)
{
//меняем направление движения
targetPoint = Game.Instance.GetRndPoint();
timeToChangeTargetPoint = 150;
var d = new PointF(targetPoint.X - Location.X, targetPoint.Y - Location.Y);
var l = d.Distance(PointF.Empty);
Velocity = new PointF(MaxSpeed*d.X/l, MaxSpeed*d.Y/l);
}
//башня - на игрока
var p = Game.Instance.PlayerTank.Location;
TowerDirection = new PointF(p.X - Location.X, p.Y - Location.Y);
//стреляем постоянно
if(!Game.Instance.PlayerTank.IsDead)
if(Game.Rnd.Next(300) == 1)
Fire();
base.Update(dt);
}
public override void OnCoollide(GameItem otherItem)
{
if (otherItem == null || otherItem.Mass >= Mass)
timeToChangeTargetPoint = 0;//будем менять направление
base.OnCoollide(otherItem);
}
} |
|
Клавиатура (контроль состояния клавиш):
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| /// <summary>
/// клавиатура
/// </summary>
public static class Keyboard
{
[Flags]
private enum KeyStates
{
None = 0,
Down = 1,
Toggled = 2
}
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern short GetKeyState(int keyCode);
private static KeyStates GetKeyState(Keys key)
{
KeyStates state = KeyStates.None;
short retVal = GetKeyState((int)key);
if ((retVal & 0x8000) == 0x8000)
state |= KeyStates.Down;
if ((retVal & 1) == 1)
state |= KeyStates.Toggled;
return state;
}
public static bool IsKeyDown(Keys key)
{
return KeyStates.Down == (GetKeyState(key) & KeyStates.Down);
}
public static bool IsKeyToggled(Keys key)
{
return KeyStates.Toggled == (GetKeyState(key) & KeyStates.Toggled);
}
} |
|
Главная форма:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| /// (C) Storm23, 2015
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication300
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
BackgroundImage = Properties.Resources.grass;
Text = "Tanks";
new Game();
MinimumSize = Game.Instance.FieldSize;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Game.MousePosition = PointToClient(MousePosition);
Game.MouseButtons = MouseButtons;
Game.Instance.Update();
Game.Instance.Draw(e.Graphics);
if (Game.Instance.PlayerTank.IsDead)
{
e.Graphics.ResetTransform();
e.Graphics.DrawString("Press Space to new Game", Font, Brushes.White, 0, 0);
}
Invalidate();
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyData == Keys.Space)
new Game();
}
}
} |
|
Анимированный спрайт:
Кликните здесь для просмотра всего текста
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| /// <summary>
/// Спрайт
/// </summary>
class Sprite
{
public Size FrameSize;
public Image Image;
public int CurrentFrame = -1;
public int FrameCount;
public Point CurrentFrameLocation
{
get
{
var framesPerRow = Image.Width / FrameSize.Width;
var x = CurrentFrame % framesPerRow;
var y = CurrentFrame / framesPerRow;
return new Point(x * FrameSize.Width, y * FrameSize.Height);
}
}
public void GotoNextFrame()
{
CurrentFrame = (CurrentFrame + 1) % FrameCount;
}
public void Draw(Graphics gr)
{
gr.DrawImage(Image, new Rectangle(Point.Empty, FrameSize), new Rectangle(CurrentFrameLocation, FrameSize), GraphicsUnit.Pixel);
}
} |
|
Проект целиком: здесь.
|