Initial commit
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
|
||||
</startup>
|
||||
</configuration>
|
|
@ -0,0 +1,6 @@
|
|||
<Application xmlns="https://github.com/avaloniaui">
|
||||
<Application.Styles>
|
||||
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
|
||||
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
|
@ -0,0 +1,27 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.BattleCity.Model;
|
||||
|
||||
namespace Avalonia.BattleCity
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.Start<MainWindow>(() =>
|
||||
{
|
||||
var field = new GameField();
|
||||
var game = new Game(field);
|
||||
game.Start();
|
||||
return field;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>
|
||||
<AssemblyName>Avalonia.BattleCity</AssemblyName>
|
||||
<OutputType>exe</OutputType>
|
||||
<PackageId>Avalonia.BattleCity</PackageId>
|
||||
<RuntimeIdentifiers>win7-x64;ubuntu.14.04-x64;osx.10.10-x64;win7-x86</RuntimeIdentifiers>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<OutputTypeEx>winexe</OutputTypeEx>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="**\*.xaml;Assets\*;Resources\*" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.4.1-*" />
|
||||
<PackageReference Include="Avalonia.Skia.Desktop" Version="0.4.1-*" Condition="'$(TargetFramework)' != 'net461'" />
|
||||
<PackageReference Include="Avalonia.Direct2D1" Version="0.4.1-*" Condition="'$(TargetFramework)' == 'net461'" />
|
||||
<PackageReference Include="Avalonia.Win32" Version="0.4.1-*" />
|
||||
<PackageReference Include="Avalonia.Gtk3" Version="0.4.1-*" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.4
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.BattleCity", "Avalonia.BattleCity.csproj", "{1CDCB582-4777-49F0-A8B5-F4D62C9BB4A0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1CDCB582-4777-49F0-A8B5-F4D62C9BB4A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1CDCB582-4777-49F0-A8B5-F4D62C9BB4A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1CDCB582-4777-49F0-A8B5-F4D62C9BB4A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1CDCB582-4777-49F0-A8B5-F4D62C9BB4A0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Markup;
|
||||
using Avalonia.BattleCity.Model;
|
||||
|
||||
namespace Avalonia.BattleCity.Infrastructure
|
||||
{
|
||||
public class CellToScreenConverter : IValueConverter
|
||||
{
|
||||
public static CellToScreenConverter Instance { get; } = new CellToScreenConverter();
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return System.Convert.ToDouble(value)*GameField.CellSize;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Markup;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.BattleCity.Model;
|
||||
|
||||
namespace Avalonia.BattleCity.Infrastructure
|
||||
{
|
||||
class DirectionToMatrixConverter : IValueConverter
|
||||
{
|
||||
public static DirectionToMatrixConverter Instance { get; } = new DirectionToMatrixConverter();
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var direction = (Facing) value;
|
||||
var matrix = Matrix.Identity;
|
||||
if (direction == Facing.South)
|
||||
{
|
||||
matrix = Matrix.CreateScale(1, -1);
|
||||
}
|
||||
if (direction == Facing.East)
|
||||
{
|
||||
matrix = Matrix.CreateRotation(1.5708);
|
||||
|
||||
}
|
||||
if (direction == Facing.West)
|
||||
{
|
||||
matrix = Matrix.CreateRotation(1.5708) * Matrix.CreateScale(-1, 1);
|
||||
}
|
||||
return new MatrixTransform(matrix);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Avalonia.BattleCity.Infrastructure
|
||||
{
|
||||
public abstract class PropertyChangedBase : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Avalonia.Markup;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.BattleCity.Model;
|
||||
|
||||
namespace Avalonia.BattleCity.Infrastructure
|
||||
{
|
||||
public class TerrainTileConverter : IValueConverter
|
||||
{
|
||||
public static TerrainTileConverter Instance { get; } = new TerrainTileConverter();
|
||||
private static Dictionary<TerrainTileType, Bitmap> _cache;
|
||||
|
||||
Dictionary<TerrainTileType, Bitmap> GetCache()
|
||||
{
|
||||
|
||||
return
|
||||
_cache ??
|
||||
(_cache = Enum.GetValues(typeof(TerrainTileType)).OfType<TerrainTileType>().ToDictionary(t => t, t =>
|
||||
new Bitmap(
|
||||
typeof(TerrainTileConverter).GetTypeInfo()
|
||||
.Assembly.GetManifestResourceStream($"Avalonia.BattleCity.Resources.{t}.png"))));
|
||||
}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => GetCache()[(TerrainTileType) value];
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
using System.Globalization;
|
||||
using Avalonia.Markup;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.BattleCity.Model;
|
||||
|
||||
namespace Avalonia.BattleCity.Infrastructure
|
||||
{
|
||||
class ZIndexConverter : IValueConverter
|
||||
{
|
||||
public static ZIndexConverter Instance { get; } = new ZIndexConverter();
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Player || value is Tank)
|
||||
return 1;
|
||||
else return 0;
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace Avalonia.BattleCity
|
||||
{
|
||||
static class Keyboard
|
||||
{
|
||||
public static readonly HashSet<Key> Keys = new HashSet<Key>();
|
||||
public static bool IsKeyDown(Key key) => Keys.Contains(key);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:local="clr-namespace:Avalonia.BattleCity;assembly=Avalonia.BattleCity"
|
||||
xmlns:model="clr-namespace:Avalonia.BattleCity.Model;assembly=Avalonia.BattleCity"
|
||||
xmlns:infrastructure="clr-namespace:Avalonia.BattleCity.Infrastructure;assembly=Avalonia.BattleCity"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
|
||||
Title="Avalonia.BattleCity" Width="640" Height="480" Design.DataContext="{Static model:GameField.DesignInstance}" >
|
||||
<Window.Styles >
|
||||
<Style Selector="ItemsControl > ContentPresenter">
|
||||
<Setter Property="ZIndex" Value="{Binding Converter={Static infrastructure:ZIndexConverter.Instance}, Path=.}"/>
|
||||
<Setter Property="Canvas.Left" Value="{Binding Location.X}"/>
|
||||
<Setter Property="Canvas.Top" Value="{Binding Location.Y}"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<ItemsControl
|
||||
Items="{Binding GameObjects}"
|
||||
Width="{Binding Width, Converter={Static infrastructure:CellToScreenConverter.Instance}, Mode=OneWay}"
|
||||
Height="{Binding Height, Converter={Static infrastructure:CellToScreenConverter.Instance}, Mode=OneWay}">
|
||||
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.DataTemplates>
|
||||
<DataTemplate DataType="{x:Type model:TerrainTile}">
|
||||
<Image Width="32" Height="32"
|
||||
Source="{Binding Type, Converter={Static infrastructure:TerrainTileConverter.Instance}}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type model:Player}">
|
||||
<Image Width="32" Height="32" Source="resm:Avalonia.BattleCity.Resources.Player.png" RenderTransform="{Binding Facing, Converter={Static infrastructure:DirectionToMatrixConverter.Instance}}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type model:Tank}">
|
||||
<Image Width="32" Height="32" Source="resm:Avalonia.BattleCity.Resources.Tank.png" RenderTransform="{Binding Facing, Converter={Static infrastructure:DirectionToMatrixConverter.Instance}}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.DataTemplates>
|
||||
</ItemsControl>
|
||||
</Window>
|
|
@ -0,0 +1,31 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Avalonia.BattleCity
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
this.AttachDevTools();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
Keyboard.Keys.Add(e.Key);
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
{
|
||||
Keyboard.Keys.Remove(e.Key);
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System.Collections.Generic;
|
||||
using Avalonia;
|
||||
using Avalonia.BattleCity.Infrastructure;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public abstract class GameObject : PropertyChangedBase
|
||||
{
|
||||
private Point _location;
|
||||
|
||||
public Point Location
|
||||
{
|
||||
get { return _location; }
|
||||
protected set
|
||||
{
|
||||
if (value.Equals(_location)) return;
|
||||
_location = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int Layer => 0;
|
||||
|
||||
protected GameObject(Point location)
|
||||
{
|
||||
Location = location;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TerrainTileType
|
||||
{
|
||||
Plain, //passable, shoot-thru
|
||||
WoodWall, //impassable, takes 1 shot to bring down
|
||||
StoneWall, //impassable, indestructible
|
||||
Water, //impassable, shoot-thru
|
||||
Pavement, //passable, 2x speed
|
||||
Forest //passable at half speed, shoot-thru
|
||||
}
|
||||
|
||||
public class TerrainTile : GameObject
|
||||
{
|
||||
private static readonly Dictionary<TerrainTileType, double> Speeds = new Dictionary<TerrainTileType, double>
|
||||
{
|
||||
{TerrainTileType.Plain, 1},
|
||||
{TerrainTileType.WoodWall, 0},
|
||||
{TerrainTileType.StoneWall, 0},
|
||||
{TerrainTileType.Water, 0},
|
||||
{TerrainTileType.Pavement, 2},
|
||||
{TerrainTileType.Forest, 0.5}
|
||||
};
|
||||
private static readonly Dictionary<TerrainTileType, bool> ShootThrus = new Dictionary<TerrainTileType, bool>
|
||||
{
|
||||
{TerrainTileType.Plain, true},
|
||||
{TerrainTileType.WoodWall, false},
|
||||
{TerrainTileType.StoneWall, false},
|
||||
{TerrainTileType.Water, true},
|
||||
{TerrainTileType.Pavement, true},
|
||||
{TerrainTileType.Forest, true},
|
||||
};
|
||||
|
||||
|
||||
public double Speed => Speeds[Type];
|
||||
public bool ShootThru => ShootThrus[Type];
|
||||
public bool IsPassable => Speed > 0.1;
|
||||
public TerrainTileType Type { get; set; }
|
||||
|
||||
public TerrainTile(Point location, TerrainTileType type) : base(location)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public class Apple : GameObject
|
||||
{
|
||||
public Apple(Point location) : base(location)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using Avalonia;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public struct CellLocation
|
||||
{
|
||||
public bool Equals(CellLocation other)
|
||||
{
|
||||
return X == other.X && Y == other.Y;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is CellLocation && Equals((CellLocation) obj);
|
||||
}
|
||||
|
||||
public static bool operator ==(CellLocation l1, CellLocation l2) => l1.Equals(l2);
|
||||
|
||||
public static bool operator !=(CellLocation l1, CellLocation l2) => !(l1 == l2);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (X*397) ^ Y;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"({X}:{Y})";
|
||||
|
||||
public Point ToPoint() => new Point(GameField.CellSize*X, GameField.CellSize*Y);
|
||||
|
||||
public CellLocation(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
|
||||
public CellLocation WithX(int x) => new CellLocation(x, Y);
|
||||
public CellLocation WithY(int y) => new CellLocation(X, y);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public enum Facing
|
||||
{
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public class Game : GameBase
|
||||
{
|
||||
private readonly GameField _field;
|
||||
|
||||
public Game(GameField field)
|
||||
{
|
||||
_field = field;
|
||||
}
|
||||
|
||||
private Random Random { get; } = new Random();
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
if (!_field.Player.IsMoving)
|
||||
{
|
||||
if (Keyboard.IsKeyDown(Key.Up))
|
||||
_field.Player.SetTarget(Facing.North);
|
||||
else if (Keyboard.IsKeyDown(Key.Down))
|
||||
_field.Player.SetTarget(Facing.South);
|
||||
else if (Keyboard.IsKeyDown(Key.Left))
|
||||
_field.Player.SetTarget(Facing.West);
|
||||
else if (Keyboard.IsKeyDown(Key.Right))
|
||||
_field.Player.SetTarget(Facing.East);
|
||||
}
|
||||
foreach (var tank in _field.GameObjects.OfType<Tank>())
|
||||
if (!tank.IsMoving)
|
||||
{
|
||||
if (!tank.SetTarget(tank.Facing))
|
||||
tank.SetTarget((Facing) Random.Next(4));
|
||||
}
|
||||
|
||||
foreach(var obj in _field.GameObjects.OfType<MovingGameObject>())
|
||||
obj.MoveToTarget();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public abstract class GameBase
|
||||
{
|
||||
public const int TicksPerSecond = 60;
|
||||
public long CurrentTick { get; private set; }
|
||||
private readonly DispatcherTimer _timer = new DispatcherTimer() {Interval = new TimeSpan(0, 0, 0, 0, 1000/TicksPerSecond)};
|
||||
|
||||
|
||||
void DoTick()
|
||||
{
|
||||
Tick();
|
||||
CurrentTick++;
|
||||
}
|
||||
|
||||
protected abstract void Tick();
|
||||
|
||||
protected GameBase()
|
||||
{
|
||||
_timer.Tick += delegate { DoTick(); };
|
||||
}
|
||||
|
||||
public void Start() => _timer.IsEnabled = true;
|
||||
public void Stop() => _timer.IsEnabled = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.BattleCity.Infrastructure;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public class GameField : PropertyChangedBase
|
||||
{
|
||||
|
||||
public static GameField DesignInstance { get; } = new GameField();
|
||||
public const double CellSize = 32;
|
||||
|
||||
public ObservableCollection<GameObject> GameObjects { get; } = new ObservableCollection<GameObject>();
|
||||
|
||||
public TerrainTile[,] Tiles { get; }
|
||||
|
||||
public Player Player { get; }
|
||||
public int Height { get; }
|
||||
public int Width{ get; }
|
||||
|
||||
public GameField() : this(20, 15)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Random Random { get; } = new Random();
|
||||
|
||||
TerrainTileType GetTypeForCoords(int x, int y)
|
||||
{
|
||||
if (x / 2 == Width / 4)
|
||||
return TerrainTileType.Pavement;
|
||||
if (y/2 == Height/4)
|
||||
{
|
||||
|
||||
return TerrainTileType.Water;
|
||||
}
|
||||
|
||||
if (x*y == 0) return TerrainTileType.StoneWall;
|
||||
if((x+1-Width)*(y+1-Height) == 0) return TerrainTileType.WoodWall;
|
||||
|
||||
|
||||
//if(Random.NextDouble()<0.1) return TerrainTileType.WoodWall;
|
||||
if(Random.NextDouble()<0.3) return TerrainTileType.Forest;
|
||||
return TerrainTileType.Plain;
|
||||
}
|
||||
|
||||
public GameField(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Tiles = new TerrainTile[width, height];
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
GameObjects.Add(
|
||||
Tiles[x, y] =
|
||||
new TerrainTile(new Point(x*CellSize, y*CellSize), GetTypeForCoords(x,y)));
|
||||
}
|
||||
}
|
||||
GameObjects.Add(
|
||||
Player = new Player(this, new CellLocation(width/2, height/2), Facing.North));
|
||||
|
||||
for (var c = 0; c < 10;)
|
||||
{
|
||||
var x = Random.Next(Width - 1);
|
||||
var y = Random.Next(Height - 1);
|
||||
if (!Tiles[x, y].IsPassable)
|
||||
continue;
|
||||
c++;
|
||||
GameObjects.Add(new Tank(this, new CellLocation(x, y), (Facing) Random.Next(4),
|
||||
Random.NextDouble()*4 + 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public abstract class MovingGameObject : GameObject
|
||||
{
|
||||
private readonly GameField _field;
|
||||
private Facing _facing;
|
||||
private CellLocation _cellLocation;
|
||||
private CellLocation _targetCellLocation;
|
||||
public override int Layer => 1;
|
||||
|
||||
public Facing Facing
|
||||
{
|
||||
get { return _facing; }
|
||||
set
|
||||
{
|
||||
if (value == _facing) return;
|
||||
_facing = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public CellLocation CellLocation
|
||||
{
|
||||
get { return _cellLocation; }
|
||||
private set
|
||||
{
|
||||
if (value.Equals(_cellLocation)) return;
|
||||
_cellLocation = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsMoving));
|
||||
}
|
||||
}
|
||||
|
||||
public CellLocation TargetCellLocation
|
||||
{
|
||||
get { return _targetCellLocation; }
|
||||
private set
|
||||
{
|
||||
if (value.Equals(_targetCellLocation)) return;
|
||||
_targetCellLocation = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsMoving));
|
||||
}
|
||||
}
|
||||
|
||||
protected MovingGameObject(GameField field, CellLocation location, Facing facing) : base(location.ToPoint())
|
||||
{
|
||||
_field = field;
|
||||
Facing = facing;
|
||||
CellLocation = TargetCellLocation = location;
|
||||
}
|
||||
|
||||
public bool IsMoving => TargetCellLocation != CellLocation;
|
||||
|
||||
public bool SetTarget(CellLocation loc)
|
||||
{
|
||||
if (IsMoving)
|
||||
//We are the bear rolling from the hill
|
||||
throw new InvalidOperationException("Unable to change direction while moving");
|
||||
if(loc == CellLocation)
|
||||
return true;
|
||||
Facing = GetDirection(CellLocation, loc);
|
||||
if (loc.X < 0 || loc.Y < 0)
|
||||
return false;
|
||||
if (loc.X >= _field.Width || loc.Y >= _field.Height)
|
||||
return false;
|
||||
if (!_field.Tiles[loc.X, loc.Y].IsPassable)
|
||||
return false;
|
||||
|
||||
if (
|
||||
_field.GameObjects.OfType<MovingGameObject>()
|
||||
.Any(t => t != this && (t.CellLocation == loc || t.TargetCellLocation == loc)))
|
||||
return false;
|
||||
|
||||
TargetCellLocation = loc;
|
||||
return true;
|
||||
}
|
||||
|
||||
public CellLocation GetTileAtDirection(Facing facing)
|
||||
{
|
||||
if (facing == Facing.North)
|
||||
return CellLocation.WithY(CellLocation.Y - 1);
|
||||
if (facing == Facing.South)
|
||||
return CellLocation.WithY(CellLocation.Y + 1);
|
||||
if (facing == Facing.West)
|
||||
|
||||
return CellLocation.WithX(CellLocation.X - 1);
|
||||
return CellLocation.WithX(CellLocation.X + 1);
|
||||
}
|
||||
|
||||
public bool SetTarget(Facing facing) => SetTarget(GetTileAtDirection(facing));
|
||||
|
||||
Facing GetDirection(CellLocation current, CellLocation target)
|
||||
{
|
||||
if (target.X < current.X)
|
||||
return Facing.West;
|
||||
if (target.X > current.X)
|
||||
return Facing.East;
|
||||
if (target.Y < current.Y)
|
||||
return Facing.North;
|
||||
return Facing.South;
|
||||
}
|
||||
|
||||
public void SetLocation(CellLocation loc)
|
||||
{
|
||||
CellLocation = loc;
|
||||
Location = loc.ToPoint();
|
||||
}
|
||||
|
||||
protected virtual double SpeedFactor => (double)1/15;
|
||||
|
||||
public void MoveToTarget()
|
||||
{
|
||||
if (TargetCellLocation == CellLocation)
|
||||
return;
|
||||
var speed = GameField.CellSize*
|
||||
(_field.Tiles[CellLocation.X, CellLocation.Y].Speed +
|
||||
_field.Tiles[TargetCellLocation.X, TargetCellLocation.Y].Speed)/2
|
||||
*SpeedFactor;
|
||||
var pos = Location;
|
||||
var direction = GetDirection(CellLocation, TargetCellLocation);
|
||||
if (direction == Facing.North)
|
||||
{
|
||||
pos = pos.WithY(pos.Y - speed);
|
||||
Location = pos;
|
||||
if (pos.Y/GameField.CellSize <= TargetCellLocation.Y)
|
||||
SetLocation(TargetCellLocation);
|
||||
}
|
||||
else if (direction == Facing.South)
|
||||
{
|
||||
pos = pos.WithY(pos.Y + speed);
|
||||
Location = pos;
|
||||
if (pos.Y / GameField.CellSize >= TargetCellLocation.Y)
|
||||
SetLocation(TargetCellLocation);
|
||||
}
|
||||
else if (direction == Facing.West)
|
||||
{
|
||||
pos = pos.WithX(pos.X - speed);
|
||||
Location = pos;
|
||||
if (pos.X / GameField.CellSize <= TargetCellLocation.X)
|
||||
SetLocation(TargetCellLocation);
|
||||
}
|
||||
else if (direction == Facing.East)
|
||||
{
|
||||
pos = pos.WithX(pos.X + speed);
|
||||
Location = pos;
|
||||
if (pos.X / GameField.CellSize >= TargetCellLocation.X)
|
||||
SetLocation(TargetCellLocation);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public class Player : MovingGameObject
|
||||
{
|
||||
public Player(GameField field, CellLocation location, Facing facing) : base(field, location, facing)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace Avalonia.BattleCity.Model
|
||||
{
|
||||
public class Tank : MovingGameObject
|
||||
{
|
||||
private readonly double _speed;
|
||||
|
||||
protected override double SpeedFactor => _speed*base.SpeedFactor;
|
||||
|
||||
public Tank(GameField field, CellLocation location, Facing facing, double speed) : base(field, location, facing)
|
||||
{
|
||||
_speed = speed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageRestore>
|
||||
<add key="enabled" value="True" />
|
||||
<add key="automatic" value="True" />
|
||||
</packageRestore>
|
||||
<packageSources>
|
||||
<add key="NuGet 3 (plain)" value="http://api.nuget.org/v3/index.json" />
|
||||
<add key="Avalonia Nightly" value="https://www.myget.org/F/avalonia-ci/api/v2" />
|
||||
</packageSources>
|
||||
<bindingRedirects>
|
||||
<add key="skip" value="False" />
|
||||
</bindingRedirects>
|
||||
</configuration>
|
|
@ -0,0 +1,14 @@
|
|||
# Avalonia.BattleCity
|
||||
|
||||
Port of https://github.com/hacklex/PekaCity to Avalonia.
|
||||
2D game stub rendered completely by AvaloniaUI
|
||||
|
||||
##What is this? Why?
|
||||
|
||||
Well, this is a stub for a 2D game. The purpose of the project was to demonstrate that one can write a 2D game in AvaloniaUI without writing any rendering code.
|
||||
## Features
|
||||
|
||||
- 2D Tiles. Not yet animated, but animating won't be a problem, I guess
|
||||
- Cell-aligned game objects
|
||||
- No rendering code, everything is done using AvaloniaUI data binding and a few ValueConverters
|
||||
|
После Ширина: | Высота: | Размер: 2.4 KiB |
После Ширина: | Высота: | Размер: 1.0 KiB |
После Ширина: | Высота: | Размер: 1.5 KiB |
После Ширина: | Высота: | Размер: 2.1 KiB |
После Ширина: | Высота: | Размер: 615 B |
После Ширина: | Высота: | Размер: 812 B |
После Ширина: | Высота: | Размер: 2.3 KiB |
После Ширина: | Высота: | Размер: 1.5 KiB |