This commit is contained in:
2025-01-06 00:45:28 +03:00
parent 27f6b11f8f
commit 0629544314
89 changed files with 2236 additions and 125198 deletions

View File

@@ -10,4 +10,9 @@
<ProjectReference Include="..\Engine\Engine.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="asset\model\" />
<Folder Include="data\" />
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,210 @@
# Blender 4.2.3 LTS
# www.blender.org
o Cube.001
v -24.000000 -6.000000 -2.000000
v -24.000000 -6.000000 6.000000
v -24.000000 -4.000000 -2.000000
v -24.000000 -4.000000 6.000000
v 24.000000 -6.000000 -2.000000
v 24.000000 -6.000000 6.000000
v 24.000000 -4.000000 -2.000000
v 24.000000 -4.000000 6.000000
s 0
f 1 2 4 3
f 3 4 8 7
f 7 8 6 5
f 5 6 2 1
f 3 7 5 1
f 8 4 2 6
o Cube.002
v -24.000000 4.000000 -2.000000
v -24.000000 4.000000 6.000000
v -24.000000 6.000000 -2.000000
v -24.000000 6.000000 6.000000
v -8.000000 4.000000 -2.000000
v -8.000000 4.000000 6.000000
v -8.000000 6.000000 -2.000000
v -8.000000 6.000000 6.000000
s 0
f 9 10 12 11
f 11 12 16 15
f 15 16 14 13
f 13 14 10 9
f 11 15 13 9
f 16 12 10 14
o Cube.003
v -24.000000 -6.000000 -2.000000
v -24.000000 -6.000000 6.000000
v -26.000000 -6.000000 -2.000000
v -26.000000 -6.000000 6.000000
v -24.000000 6.000000 -2.000000
v -24.000000 6.000000 6.000000
v -26.000000 6.000000 -2.000000
v -26.000000 6.000000 6.000000
s 0
f 17 18 20 19
f 19 20 24 23
f 23 24 22 21
f 21 22 18 17
f 19 23 21 17
f 24 20 18 22
o Cube.004
v -8.000000 6.000000 -2.000000
v -8.000000 6.000000 6.000000
v -10.000000 6.000000 -2.000000
v -10.000000 6.000000 6.000000
v -8.000000 45.031250 -2.000000
v -8.000000 45.031250 6.000000
v -10.000000 45.031250 -2.000000
v -10.000000 45.031250 6.000000
s 0
f 25 26 28 27
f 27 28 32 31
f 31 32 30 29
f 29 30 26 25
f 27 31 29 25
f 32 28 26 30
o Cube.005
v 50.000000 47.031250 -2.000000
v 50.000000 47.031250 6.000000
v 50.000000 45.031250 -2.000000
v 50.000000 45.031250 6.000000
v -10.000002 47.031250 -2.000000
v -10.000002 47.031250 6.000000
v -10.000002 45.031250 -2.000000
v -10.000002 45.031250 6.000000
s 0
f 33 34 36 35
f 35 36 40 39
f 39 40 38 37
f 37 38 34 33
f 35 39 37 33
f 40 36 34 38
o Cube.006
v 12.450001 4.000000 -2.000000
v 12.450001 4.000000 6.000000
v 8.000000 4.000000 -2.000000
v 8.000000 4.000000 6.000000
v 12.450001 8.031248 -2.000000
v 12.450001 8.031248 6.000000
v 8.000000 8.031248 -2.000000
v 8.000000 8.031248 6.000000
s 0
f 41 42 44 43
f 43 44 48 47
f 47 48 46 45
f 45 46 42 41
f 43 47 45 41
f 48 44 42 46
o Cube.007
v 11.200001 8.031250 -2.000000
v 11.200001 8.031250 6.000000
v 8.000000 8.031248 -2.000000
v 8.000000 8.031248 6.000000
v 11.200001 37.031250 -2.000000
v 11.200001 37.031250 6.000000
v 8.000000 37.031250 -2.000000
v 8.000000 37.031250 6.000000
s 0
f 49 50 52 51
f 51 52 56 55
f 55 56 54 53
f 53 54 50 49
f 51 55 53 49
f 56 52 50 54
o Cube.008
v 26.200001 27.031250 -2.000000
v 26.200001 27.031250 6.000000
v 11.200001 27.031250 -2.000000
v 11.200001 27.031250 6.000000
v 26.200001 37.031250 -2.000000
v 26.200001 37.031250 6.000000
v 11.200001 37.031250 -2.000000
v 11.200001 37.031250 6.000000
s 0
f 57 58 60 59
f 59 60 64 63
f 63 64 62 61
f 61 62 58 57
f 59 63 61 57
f 64 60 58 62
o Cube.009
v 26.000000 -6.000000 -2.000000
v 26.000000 -6.000000 6.000000
v 24.000000 -6.000000 -2.000000
v 24.000000 -6.000000 6.000000
v 26.000000 4.000000 -2.000000
v 26.000000 4.000000 6.000000
v 24.000000 4.000000 -2.000000
v 24.000000 4.000000 6.000000
s 0
f 65 66 68 67
f 67 68 72 71
f 71 72 70 69
f 69 70 66 65
f 67 71 69 65
f 72 68 66 70
o Cube.010
v 31.799999 4.000002 -2.000000
v 31.799999 4.000002 6.000000
v 17.450001 4.000000 -2.000000
v 17.450001 4.000000 6.000000
v 31.799999 8.031251 -2.000000
v 31.799999 8.031251 6.000000
v 17.450001 8.031249 -2.000000
v 17.450001 8.031249 6.000000
s 0
f 73 74 76 75
f 75 76 80 79
f 79 80 78 77
f 77 78 74 73
f 75 79 77 73
f 80 76 74 78
o Cube.011
v 33.799999 6.000000 -2.000000
v 33.799999 6.000000 6.000000
v 31.799999 6.000000 -2.000000
v 31.799999 6.000000 6.000000
v 33.799999 37.031250 -2.000000
v 33.799999 37.031250 6.000000
v 31.799999 37.031250 -2.000000
v 31.799999 37.031250 6.000000
s 0
f 81 82 84 83
f 83 84 88 87
f 87 88 86 85
f 85 86 82 81
f 83 87 85 81
f 88 84 82 86
o Cube.012
v 33.799999 35.031250 -2.000000
v 33.799999 35.031250 6.000000
v 33.799999 37.031250 -2.000000
v 33.799999 37.031250 6.000000
v 50.000000 35.031250 -2.000000
v 50.000000 35.031250 6.000000
v 50.000000 37.031250 -2.000000
v 50.000000 37.031250 6.000000
s 0
f 89 90 92 91
f 91 92 96 95
f 95 96 94 93
f 93 94 90 89
f 91 95 93 89
f 96 92 90 94
o Cube.013
v 52.000000 35.031250 -2.000000
v 52.000000 35.031250 6.000000
v 50.000000 35.031250 -2.000000
v 50.000000 35.031250 6.000000
v 52.000000 47.031250 -2.000000
v 52.000000 47.031250 6.000000
v 50.000000 47.031250 -2.000000
v 50.000000 47.031250 6.000000
s 0
f 97 98 100 99
f 99 100 104 103
f 103 104 102 101
f 101 102 98 97
f 99 103 101 97
f 104 100 98 102

View File

@@ -0,0 +1,238 @@
# Blender 4.2.3 LTS
# www.blender.org
o Plane
v -24.000000 -4.000000 0.000000
v 24.000000 -4.000000 0.000000
v -24.000000 4.000000 0.000000
v 24.000000 4.000000 0.000000
v -8.000000 -4.000000 0.000000
v 8.000000 -4.000000 0.000000
v 8.000000 4.000000 0.000000
v -8.000000 4.000000 0.000000
v 8.000000 37.031250 0.000000
v -8.000000 37.031250 0.000000
v 8.000000 45.031250 0.000000
v -8.000000 45.031250 0.000000
v 50.000000 37.031250 0.000000
v 50.000000 45.031250 0.000000
v 26.200001 37.031250 0.000000
v 31.799999 37.031250 0.000000
v 31.799999 45.031250 0.000000
v 26.200001 45.031250 0.000000
v 26.200001 27.031250 0.000000
v 31.799999 27.031250 0.000000
v 26.200001 8.031250 0.000000
v 31.799999 8.031250 0.000000
v 11.200001 27.031250 0.000000
v 11.200001 8.031250 0.000000
v 17.450001 27.031250 0.000000
v 12.450001 27.031250 0.000000
v 12.450001 8.031250 0.000000
v 17.450001 8.031250 0.000000
v 12.450001 4.000000 0.000000
v 17.450001 4.000000 0.000000
v 12.450001 -4.000000 0.000000
v 17.450001 -4.000000 0.000000
v -24.000000 -4.000000 4.000000
v 24.000000 -4.000000 4.000000
v -24.000000 4.000000 4.000000
v 24.000000 4.000000 4.000000
v -8.000000 -4.000000 4.000000
v 8.000000 -4.000000 4.000000
v 8.000000 4.000000 4.000000
v -8.000000 4.000000 4.000000
v 8.000000 37.031250 4.000000
v -8.000000 37.031250 4.000000
v 8.000000 45.031250 4.000000
v -8.000000 45.031250 4.000000
v 50.000000 37.031250 4.000000
v 50.000000 45.031250 4.000000
v 26.200001 37.031250 4.000000
v 31.799999 37.031250 4.000000
v 31.799999 45.031250 4.000000
v 26.200001 45.031250 4.000000
v 26.200001 27.031250 4.000000
v 31.799999 27.031250 4.000000
v 26.200001 8.031250 4.000000
v 31.799999 8.031250 4.000000
v 11.200001 27.031250 4.000000
v 11.200001 8.031250 4.000000
v 17.450001 27.031250 4.000000
v 12.450001 27.031250 4.000000
v 12.450001 8.031250 4.000000
v 17.450001 8.031250 4.000000
v 12.450001 4.000000 4.000000
v 17.450001 4.000000 4.000000
v 12.450001 -4.000000 4.000000
v 17.450001 -4.000000 4.000000
vn -0.0000 -0.0000 -1.0000
vn -0.0000 -0.0000 1.0000
vn 1.0000 -0.0000 -0.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vt 0.757888 0.095816
vt 0.900966 0.310434
vt 0.757888 0.310434
vt 0.829427 0.095816
vt 0.829427 0.310434
vt 0.900966 0.095816
vt 0.871139 0.898505
vt 0.787715 0.773370
vt 0.829427 0.773370
vt 0.787715 0.898505
vt 0.871139 0.773370
vt 0.829427 0.898505
vt 0.123250 0.042329
vt 0.123250 0.985848
vt 0.074426 0.042329
vt 0.074426 0.985846
vt 0.178788 0.042329
vt 0.178788 0.985846
vt 0.123250 0.985846
vt 0.025602 0.042329
vt 0.025602 0.985848
vt 0.195876 0.042329
vt 0.195876 0.985848
vt 0.136830 0.042329
vt 0.136830 0.985848
vt 0.152087 0.042329
vt 0.172074 0.985848
vt 0.172074 0.042329
vt 0.178788 0.985848
vt 0.152087 0.985848
vt 0.251414 0.042329
vt 0.251414 0.985846
vt 0.074426 0.985848
vt 0.195876 0.985846
vt 0.133015 0.042329
vt 0.133015 0.985848
s 0
f 4/1/1 32/2/1 30/2/1
f 3/3/1 5/4/1 1/1/1
f 8/5/1 6/6/1 5/4/1
f 10/5/1 7/2/1 8/5/1
f 12/5/1 9/2/1 10/5/1
f 14/2/1 16/2/1 17/2/1
f 18/2/1 9/2/1 11/2/1
f 17/2/1 15/2/1 18/2/1
f 20/2/1 15/2/1 16/2/1
f 22/2/1 19/2/1 20/2/1
f 24/2/1 26/2/1 27/2/1
f 28/2/1 19/2/1 21/2/1
f 27/2/1 25/2/1 28/2/1
f 30/2/1 27/2/1 28/2/1
f 32/2/1 29/2/1 30/2/1
f 7/2/1 31/2/1 6/6/1
f 64/7/2 36/8/2 62/7/2
f 37/9/2 35/10/2 33/8/2
f 38/11/2 40/12/2 37/9/2
f 39/7/2 42/12/2 40/12/2
f 41/7/2 44/12/2 42/12/2
f 48/7/2 46/7/2 49/7/2
f 41/7/2 50/7/2 43/7/2
f 47/7/2 49/7/2 50/7/2
f 47/7/2 52/7/2 48/7/2
f 51/7/2 54/7/2 52/7/2
f 58/7/2 56/7/2 59/7/2
f 51/7/2 60/7/2 53/7/2
f 57/7/2 59/7/2 60/7/2
f 59/7/2 62/7/2 60/7/2
f 61/7/2 64/7/2 62/7/2
f 63/7/2 39/7/2 38/11/2
f 9/13/3 39/14/3 7/13/3
f 8/15/4 42/16/4 10/15/4
f 19/17/4 47/18/4 15/17/4
f 12/15/5 43/19/5 11/13/5
f 1/20/4 35/21/4 3/20/4
f 16/22/3 52/23/3 20/22/3
f 29/24/4 59/25/4 27/24/4
f 30/26/5 36/27/5 4/28/5
f 22/22/6 53/29/6 21/17/6
f 10/15/4 44/16/4 12/15/4
f 28/26/3 62/30/3 30/26/3
f 4/28/3 34/27/3 2/28/3
f 14/31/3 45/32/3 13/31/3
f 32/26/6 63/25/6 31/24/6
f 3/20/5 40/33/5 8/15/5
f 20/22/3 54/23/3 22/22/3
f 13/31/6 48/34/6 16/22/6
f 5/15/6 33/21/6 1/20/6
f 24/35/4 55/36/4 23/35/4
f 11/13/5 50/18/5 18/17/5
f 6/13/6 37/33/6 5/15/6
f 23/35/5 58/25/5 26/24/5
f 15/17/6 41/19/6 9/13/6
f 31/24/6 38/14/6 6/13/6
f 2/28/6 64/30/6 32/26/6
f 21/17/6 60/30/6 28/26/6
f 7/13/5 61/25/5 29/24/5
f 25/26/5 51/29/5 19/17/5
f 17/22/5 46/32/5 14/31/5
f 26/24/5 57/30/5 25/26/5
f 18/17/5 49/34/5 17/22/5
f 27/24/6 56/36/6 24/35/6
f 4/1/1 2/1/1 32/2/1
f 3/3/1 8/5/1 5/4/1
f 8/5/1 7/2/1 6/6/1
f 10/5/1 9/2/1 7/2/1
f 12/5/1 11/2/1 9/2/1
f 14/2/1 13/2/1 16/2/1
f 18/2/1 15/2/1 9/2/1
f 17/2/1 16/2/1 15/2/1
f 20/2/1 19/2/1 15/2/1
f 22/2/1 21/2/1 19/2/1
f 24/2/1 23/2/1 26/2/1
f 28/2/1 25/2/1 19/2/1
f 27/2/1 26/2/1 25/2/1
f 30/2/1 29/2/1 27/2/1
f 32/2/1 31/2/1 29/2/1
f 7/2/1 29/2/1 31/2/1
f 64/7/2 34/8/2 36/8/2
f 37/9/2 40/12/2 35/10/2
f 38/11/2 39/7/2 40/12/2
f 39/7/2 41/7/2 42/12/2
f 41/7/2 43/7/2 44/12/2
f 48/7/2 45/7/2 46/7/2
f 41/7/2 47/7/2 50/7/2
f 47/7/2 48/7/2 49/7/2
f 47/7/2 51/7/2 52/7/2
f 51/7/2 53/7/2 54/7/2
f 58/7/2 55/7/2 56/7/2
f 51/7/2 57/7/2 60/7/2
f 57/7/2 58/7/2 59/7/2
f 59/7/2 61/7/2 62/7/2
f 61/7/2 63/7/2 64/7/2
f 63/7/2 61/7/2 39/7/2
f 9/13/3 41/19/3 39/14/3
f 8/15/4 40/33/4 42/16/4
f 19/17/4 51/29/4 47/18/4
f 12/15/5 44/16/5 43/19/5
f 1/20/4 33/21/4 35/21/4
f 16/22/3 48/34/3 52/23/3
f 29/24/4 61/25/4 59/25/4
f 30/26/5 62/30/5 36/27/5
f 22/22/6 54/23/6 53/29/6
f 10/15/4 42/16/4 44/16/4
f 28/26/3 60/30/3 62/30/3
f 4/28/3 36/27/3 34/27/3
f 14/31/3 46/32/3 45/32/3
f 32/26/6 64/30/6 63/25/6
f 3/20/5 35/21/5 40/33/5
f 20/22/3 52/23/3 54/23/3
f 13/31/6 45/32/6 48/34/6
f 5/15/6 37/33/6 33/21/6
f 24/35/4 56/36/4 55/36/4
f 11/13/5 43/19/5 50/18/5
f 6/13/6 38/14/6 37/33/6
f 23/35/5 55/36/5 58/25/5
f 15/17/6 47/18/6 41/19/6
f 31/24/6 63/25/6 38/14/6
f 2/28/6 34/27/6 64/30/6
f 21/17/6 53/29/6 60/30/6
f 7/13/5 39/14/5 61/25/5
f 25/26/5 57/30/5 51/29/5
f 17/22/5 49/34/5 46/32/5
f 26/24/5 58/25/5 57/30/5
f 18/17/5 50/18/5 49/34/5
f 27/24/6 59/25/6 56/36/6

View File

@@ -0,0 +1,10 @@
# Blender 4.2.3 LTS
# www.blender.org
o Spawners
v -21.000000 0.000000 0.000000
v -5.000000 41.000000 0.000000
v 48.000000 41.000000 0.000000
v 20.000000 0.000000 0.000000
v 15.000000 23.000000 0.000000
s 0
f 5 3 4 1 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,12 @@
# Blender 4.2.3 LTS
# www.blender.org
o Valuables
v 0.000000 0.000000 0.000000
v 25.000000 40.000000 0.000000
v 0.000000 40.000000 0.000000
v 0.000000 20.000000 0.000000
v 21.000000 17.000000 0.000000
v 15.000000 0.000000 0.000000
v -16.000000 0.000000 0.000000
s 0
f 3 2 5 6 1 7 4

View File

@@ -1,101 +0,0 @@
# Blender 4.2.3 LTS
# www.blender.org
o Plane
v -14.316629 -4.684887 0.000000
v 14.316629 -4.684887 0.000000
v -14.316629 4.684887 0.000000
v 14.316629 4.684887 0.000000
v 6.822107 -4.684887 0.000000
v 6.822107 4.684887 0.000000
v 14.316629 33.607601 0.000000
v 6.822107 33.607601 0.000000
v 14.316629 25.280016 0.000000
v 6.822107 25.280016 0.000000
v -6.847844 33.607601 0.000000
v -6.847844 25.280016 0.000000
v -25.254845 24.277735 0.000000
v -18.173466 20.114567 0.000000
v -26.336294 7.837210 0.000000
v -19.225933 10.395360 0.000000
v -14.316629 -4.684887 3.000000
v 14.316629 -4.684887 3.000000
v 14.316629 4.684887 3.000000
v 6.822107 -4.684887 3.000000
v 14.316629 33.607601 3.000000
v 6.822107 33.607601 3.000000
v 14.316629 25.280016 3.000000
v -6.847844 33.607601 3.000000
v -25.254845 24.277735 3.000000
v -26.336294 7.837210 3.000000
v -14.316629 4.684887 3.000000
v 6.822107 4.684887 3.000000
v 6.822107 25.280016 3.000000
v -6.847844 25.280016 3.000000
v -18.173466 20.114567 3.000000
v -19.225933 10.395360 3.000000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn 0.7214 0.6925 -0.0000
vn 0.9978 -0.0656 -0.0000
vn 0.4521 -0.8920 -0.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vn -0.7583 -0.6519 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.4150 0.9098 -0.0000
vn -0.9942 0.1077 -0.0000
vt 1.000000 0.000000
vt 0.738258 1.000000
vt 0.738258 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 1.000000
s 0
f 2/1/1 6/2/1 5/3/1
f 5/3/1 3/4/1 1/5/1
f 9/6/1 8/2/1 10/2/1
f 4/6/1 10/2/1 6/2/1
f 8/2/1 12/2/1 10/2/1
f 11/2/1 14/2/1 12/2/1
f 14/2/1 15/2/1 16/2/1
f 3/4/1 15/2/1 1/5/1
f 2/1/2 19/6/2 4/6/2
f 15/2/3 17/5/3 1/5/3
f 13/2/4 26/2/4 15/2/4
f 4/6/2 23/6/2 9/6/2
f 11/2/5 25/2/5 13/2/5
f 7/6/6 22/2/6 8/2/6
f 8/2/6 24/2/6 11/2/6
f 1/5/7 20/3/7 5/3/7
f 5/3/7 18/1/7 2/1/7
f 9/6/2 21/6/2 7/6/2
f 12/2/7 29/2/7 10/2/7
f 3/4/8 32/2/8 16/2/8
f 10/2/9 28/2/9 6/2/9
f 14/2/10 30/2/10 12/2/10
f 6/2/6 27/4/6 3/4/6
f 16/2/11 31/2/11 14/2/11
f 2/1/1 4/6/1 6/2/1
f 5/3/1 6/2/1 3/4/1
f 9/6/1 7/6/1 8/2/1
f 4/6/1 9/6/1 10/2/1
f 8/2/1 11/2/1 12/2/1
f 11/2/1 13/2/1 14/2/1
f 14/2/1 13/2/1 15/2/1
f 3/4/1 16/2/1 15/2/1
f 2/1/2 18/1/2 19/6/2
f 15/2/3 26/2/3 17/5/3
f 13/2/4 25/2/4 26/2/4
f 4/6/2 19/6/2 23/6/2
f 11/2/5 24/2/5 25/2/5
f 7/6/6 21/6/6 22/2/6
f 8/2/6 22/2/6 24/2/6
f 1/5/7 17/5/7 20/3/7
f 5/3/7 20/3/7 18/1/7
f 9/6/2 23/6/2 21/6/2
f 12/2/7 30/2/7 29/2/7
f 3/4/8 27/4/8 32/2/8
f 10/2/9 29/2/9 28/2/9
f 14/2/10 31/2/10 30/2/10
f 6/2/6 28/2/6 27/4/6
f 16/2/11 32/2/11 31/2/11

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
# Blender 4.2.3 LTS
# www.blender.org
o Cube
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.625000 0.000000
vt 0.375000 0.250000
vt 0.375000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.500000
vt 0.625000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.875000 0.500000
vt 0.625000 1.000000
vt 0.875000 0.750000
s 0
f 2/1/1 3/2/1 1/3/1
f 4/4/2 7/5/2 3/2/2
f 8/6/3 5/7/3 7/5/3
f 6/8/4 1/9/4 5/7/4
f 7/5/5 1/10/5 3/11/5
f 4/12/6 6/8/6 8/6/6
f 2/1/1 4/4/1 3/2/1
f 4/4/2 8/6/2 7/5/2
f 8/6/3 6/8/3 5/7/3
f 6/8/4 2/13/4 1/9/4
f 7/5/5 5/7/5 1/10/5
f 4/12/6 2/14/6 6/8/6

View File

@@ -1,38 +0,0 @@
# Blender 4.2.3 LTS
# www.blender.org
o Cube
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
vt 0.625000 0.000000
vt 0.375000 0.250000
vt 0.375000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.500000
vt 0.625000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.875000 0.500000
vt 0.625000 1.000000
vt 0.875000 0.750000
s 0
f 2/1 3/2 1/3
f 4/4 7/5 3/2
f 8/6 5/7 7/5
f 6/8 1/9 5/7
f 7/5 1/10 3/11
f 4/12 6/8 8/6
f 2/1 4/4 3/2
f 4/4 8/6 7/5
f 8/6 6/8 5/7
f 6/8 2/13 1/9
f 7/5 5/7 1/10
f 4/12 2/14 6/8

View File

@@ -1,24 +0,0 @@
# Blender 4.2.3 LTS
# www.blender.org
o Cube
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
s 0
f 2 3 1
f 4 7 3
f 8 5 7
f 6 1 5
f 7 1 3
f 4 6 8
f 2 4 3
f 4 8 7
f 8 6 5
f 6 2 1
f 7 5 1
f 4 2 6

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,75 @@
using DoomDeathmatch.Component.MVC.Model.Enemy;
using DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
using DoomDeathmatch.Component.MVC.View;
using DoomDeathmatch.Component.Util;
namespace DoomDeathmatch.Component.MVC.Controller;
public class EnemyController : Engine.Scene.Component.Component
{
public HealthController HealthController => _healthController;
private GameController _gameController = null!;
private HealthController _healthController = null!;
private EnemyView _enemyView = null!;
private MovementController _movementController = null!;
private AttackBehavior _attackBehavior = null!;
private readonly EnemyData _enemyData;
public EnemyController(EnemyData parEnemyData)
{
_enemyData = parEnemyData;
}
public override void Awake()
{
_gameController = GameObject.Scene!.FindFirstComponent<GameController>()!;
_healthController = GameObject.GetComponent<HealthController>()!;
_enemyView = GameObject.GetComponent<EnemyView>()!;
_movementController = GameObject.GetComponent<MovementController>()!;
ArgumentNullException.ThrowIfNull(_gameController);
ArgumentNullException.ThrowIfNull(_healthController);
ArgumentNullException.ThrowIfNull(_enemyView);
ArgumentNullException.ThrowIfNull(_movementController);
_attackBehavior = _enemyData.AttackBehaviorCreator.Create(this, _gameController.PlayerController.HealthController);
_healthController.SetMaxHealth(_enemyData.BaseHealth);
_healthController.OnDeath += OnDeath;
_enemyView.UpdateView(_enemyData);
_movementController.Speed = _enemyData.BaseSpeed;
}
public override void Start()
{
var billboardComponent = GameObject.GetComponentInChildren<BillboardComponent>();
if (billboardComponent == null)
return;
billboardComponent.Target = _gameController.PlayerController.Camera.GameObject.Transform;
}
public override void Update(double parDeltaTime)
{
var playerPosition = _gameController.PlayerController.GameObject.Transform.Translation;
var enemyPosition = GameObject.Transform.Translation;
var nextPosition = _enemyData.MovementBehavior.GetNextPosition(enemyPosition, playerPosition);
if (enemyPosition != nextPosition)
{
var direction = (nextPosition - enemyPosition).Normalized();
_movementController.ApplyMovement(direction);
}
_attackBehavior.Attack(parDeltaTime);
}
private void OnDeath()
{
GameObject.Scene!.Remove(GameObject);
_gameController.ScoreController.AddScore(_enemyData.BaseScore);
}
}

View File

@@ -0,0 +1,64 @@
using DoomDeathmatch.Component.UI;
using Engine.Input;
namespace DoomDeathmatch.Component.MVC.Controller;
public class GameController : Engine.Scene.Component.Component
{
public bool IsPaused { get; set; } = false;
public PlayerController PlayerController => _playerController;
public ScoreController ScoreController => _scoreController;
private ScoreController _scoreController = null!;
private TimerController _timerController = null!;
private PlayerController _playerController = null!;
private readonly MenuControllerComponent _menuController;
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
public GameController(MenuControllerComponent parMenuController)
{
_menuController = parMenuController;
}
public override void Awake()
{
_scoreController = GameObject.GetComponent<ScoreController>()!;
_timerController = GameObject.GetComponent<TimerController>()!;
_playerController = GameObject.Scene!.FindFirstComponent<PlayerController>()!;
ArgumentNullException.ThrowIfNull(_scoreController);
ArgumentNullException.ThrowIfNull(_timerController);
ArgumentNullException.ThrowIfNull(_playerController);
}
public void Unpause()
{
GameObject.Scene!.TimeScale = 1.0f;
IsPaused = false;
_menuController.SelectMenuItem("play");
}
public void Pause()
{
GameObject.Scene!.TimeScale = 0.0f;
IsPaused = true;
_menuController.SelectMenuItem("escape");
}
public override void Update(double parDeltaTime)
{
if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Escape))
{
if (IsPaused)
{
Unpause();
}
else
{
Pause();
}
}
}
}

View File

@@ -5,11 +5,19 @@ namespace DoomDeathmatch.Component.MVC.Controller;
public class HealthController : Engine.Scene.Component.Component
{
private readonly HealthModel _healthModel = new(100);
public event Action? OnDeath;
private readonly HealthModel _healthModel;
private HealthView? _healthView;
public HealthController(float parHealth = 100)
{
_healthModel = new HealthModel(parHealth);
}
public override void Awake()
{
_healthModel.HealthChanged += OnHealthChanged;
_healthView = GameObject.GetComponent<HealthView>();
if (_healthView != null)
@@ -19,6 +27,12 @@ public class HealthController : Engine.Scene.Component.Component
}
}
public void SetMaxHealth(float parMaxHealth)
{
_healthModel.MaxHealth = parMaxHealth;
_healthModel.Health = parMaxHealth;
}
public void TakeDamage(float parDamage)
{
_healthModel.Health -= parDamage;
@@ -28,4 +42,12 @@ public class HealthController : Engine.Scene.Component.Component
{
_healthModel.Health += parHeal;
}
private void OnHealthChanged(HealthModel parHealthModel)
{
if (parHealthModel.Health <= 0)
{
OnDeath?.Invoke();
}
}
}

View File

@@ -0,0 +1,26 @@
using DoomDeathmatch.Component.Util;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Controller;
public class MovementController : Engine.Scene.Component.Component
{
public float Speed { get; set; } = 10.0f;
private RigidbodyComponent _rigidbody = null!;
private DragComponent _dragComponent = null!;
public override void Awake()
{
_rigidbody = GameObject.GetComponent<RigidbodyComponent>()!;
_dragComponent = GameObject.GetComponent<DragComponent>()!;
ArgumentNullException.ThrowIfNull(_rigidbody);
ArgumentNullException.ThrowIfNull(_dragComponent);
}
public void ApplyMovement(Vector3 parDirection)
{
_rigidbody.AddForce(_dragComponent.Drag * Speed * parDirection);
}
}

View File

@@ -1,29 +1,45 @@
using DoomDeathmatch.Component.MVC.Model;
using DoomDeathmatch.Component.MVC.Model.Weapon;
using DoomDeathmatch.Component.Util;
using DoomDeathmatch.Component.Util.Collision;
using Engine.Graphics.Pipeline;
using Engine.Input;
using Engine.Scene.Component.BuiltIn;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Controller;
public class PlayerController : Engine.Scene.Component.Component
{
public bool IsPaused { get; set; } = false;
public HealthController HealthController => _healthController;
public PerspectiveCamera Camera => _camera;
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
private HealthController _healthController = null!;
private WeaponController _weaponController = null!;
private ScoreController _scoreController = null!;
private readonly PerspectiveCamera _camera;
public PlayerController(PerspectiveCamera parCamera)
{
_camera = parCamera;
}
public override void Awake()
{
_healthController = GameObject.GetComponent<HealthController>()!;
_weaponController = GameObject.GetComponent<WeaponController>()!;
_scoreController = GameObject.GetComponent<ScoreController>()!;
ArgumentNullException.ThrowIfNull(_healthController);
ArgumentNullException.ThrowIfNull(_weaponController);
ArgumentNullException.ThrowIfNull(_scoreController);
}
public override void Update(double parDeltaTime)
{
if (IsPaused)
return;
if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.C))
_weaponController.AddWeapon(WeaponData.Shotgun);
@@ -40,6 +56,42 @@ public class PlayerController : Engine.Scene.Component.Component
}
if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Space))
_weaponController.TryShoot();
{
if (!_weaponController.TryShoot())
return;
var position = _camera.GameObject.Transform.GetFullTranslation();
var forward = (_camera.Forward - position).Normalized();
var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized();
var collisionManager = GameObject.Scene!.FindFirstComponent<CollisionManager>();
var offsets = _weaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right);
foreach (var offset in offsets)
{
var direction = forward + offset;
if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result))
continue;
var hitDisplayObject = GameObjectUtil.CreateGameObject(GameObject.Scene,
new Transform { Translation = result.HitPoint, Size = new Vector3(0.1f) },
[
new Box2DRenderer { Color = new Vector4(0, 0, 1, 1), RenderLayer = RenderLayer.DEFAULT },
new BillboardComponent { Target = _camera.GameObject.Transform }
]
);
var hitNormalDisplayObject = GameObjectUtil.CreateGameObject(GameObject.Scene,
new Transform { Translation = result.HitPoint + result.Normal, Size = new Vector3(0.1f) },
[
new Box2DRenderer { Color = new Vector4(1, 0, 1, 1), RenderLayer = RenderLayer.DEFAULT },
new BillboardComponent { Target = _camera.GameObject.Transform }
]
);
var enemyController = result.HitObject.GetComponent<EnemyController>();
enemyController?.HealthController.TakeDamage(_weaponController.WeaponData.Damage);
}
}
}
}

View File

@@ -5,6 +5,8 @@ namespace DoomDeathmatch.Component.MVC.Controller;
public class ScoreController : Engine.Scene.Component.Component
{
public int Score => _scoreModel.Score;
private readonly ScoreModel _scoreModel = new();
private ScoreView _scoreView = null!;

View File

@@ -0,0 +1,35 @@
using DoomDeathmatch.Component.MVC.View;
using Engine.Util;
namespace DoomDeathmatch.Component.MVC.Controller;
public class TimerController : Engine.Scene.Component.Component
{
public bool IsPaused { get; set; } = false;
public event Action? OnFinished;
private readonly TickableTimer _tickableTimer = new(60 + 10);
private TimerView _timerView = null!;
public override void Awake()
{
_timerView = GameObject.GetComponent<TimerView>()!;
_timerView.UpdateView(_tickableTimer.CurrentTime);
_tickableTimer.OnUpdate += OnTimeChanged;
_tickableTimer.OnFinished += OnFinished;
}
public override void Update(double parDeltaTime)
{
if (IsPaused)
return;
_tickableTimer.Update(parDeltaTime);
}
private void OnTimeChanged(double parTime)
{
_timerView.UpdateView(parTime);
}
}

View File

@@ -1,4 +1,5 @@
using DoomDeathmatch.Component.MVC.Model;
using DoomDeathmatch.Component.MVC.Model.Weapon;
using DoomDeathmatch.Component.MVC.View;
namespace DoomDeathmatch.Component.MVC.Controller;
@@ -6,6 +7,7 @@ namespace DoomDeathmatch.Component.MVC.Controller;
public class WeaponController : Engine.Scene.Component.Component
{
public event Action<WeaponData>? OnWeaponShot;
public WeaponData WeaponData => _weaponModel.SelectedWeapon;
private readonly WeaponModel _weaponModel = new();
private WeaponView _weaponView = null!;
@@ -24,7 +26,7 @@ public class WeaponController : Engine.Scene.Component.Component
if (_weaponModel.SelectedWeapon.Ammo <= 0)
return false;
_weaponModel.SelectedWeapon.Ammo--;
// _weaponModel.SelectedWeapon.Ammo--;
OnWeaponShot?.Invoke(_weaponModel.SelectedWeapon);

View File

@@ -0,0 +1,11 @@
using DoomDeathmatch.Component.MVC.Controller;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public abstract class AttackBehavior(EnemyController parEnemyController, HealthController parHealthController)
{
protected readonly EnemyController _enemyController = parEnemyController;
protected readonly HealthController _healthController = parHealthController;
public abstract bool Attack(double parDeltaTime);
}

View File

@@ -0,0 +1,26 @@
using DoomDeathmatch.Component.MVC.Controller;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public class CloseContinuousAttackBehavior(
EnemyController parEnemyController,
HealthController parHealthController,
float parRadius,
float parDamage)
: AttackBehavior(parEnemyController, parHealthController)
{
public override bool Attack(double parDeltaTime)
{
var distanceSquared =
(_enemyController.GameObject.Transform.Translation - _healthController.GameObject.Transform.Translation)
.LengthSquared;
if (distanceSquared <= parRadius * parRadius)
{
_healthController.TakeDamage(parDamage * (float)parDeltaTime);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,29 @@
using DoomDeathmatch.Component.MVC.Controller;
using Engine.Util;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public class CloseCooldownAttackBehavior(
EnemyController parEnemyController,
HealthController parHealthController,
float parRadius,
float parCooldown,
float parDamage)
: CooldownAttackBehavior(parEnemyController, parHealthController, parCooldown)
{
protected override bool CanAttack()
{
var distanceSquared =
(_enemyController.GameObject.Transform.Translation - _healthController.GameObject.Transform.Translation)
.LengthSquared;
return distanceSquared <= parRadius * parRadius;
}
protected override bool ActivateAttack()
{
_healthController.TakeDamage(parDamage);
return true;
}
}

View File

@@ -0,0 +1,22 @@
using DoomDeathmatch.Component.MVC.Controller;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public class CompositeAttackBehavior(
EnemyController parEnemyController,
HealthController parHealthController,
List<AttackBehavior> parBehaviors)
: AttackBehavior(parEnemyController, parHealthController)
{
public override bool Attack(double parDeltaTime)
{
var result = false;
foreach (var behavior in parBehaviors)
{
result |= behavior.Attack(parDeltaTime);
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
using DoomDeathmatch.Component.MVC.Controller;
using Engine.Util;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public abstract class CooldownAttackBehavior(
EnemyController parEnemyController,
HealthController parHealthController,
float parCooldown)
: AttackBehavior(parEnemyController, parHealthController)
{
private readonly TickableTimer _tickableTimer = new(parCooldown);
public sealed override bool Attack(double parDeltaTime)
{
_tickableTimer.Update(parDeltaTime);
if (CanAttack())
{
if (!_tickableTimer.IsFinished)
return false;
var result = ActivateAttack();
_tickableTimer.Reset();
return result;
}
return false;
}
protected abstract bool CanAttack();
protected abstract bool ActivateAttack();
}

View File

@@ -0,0 +1,12 @@
using DoomDeathmatch.Component.MVC.Controller;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public class FuncAttackBehaviorCreator(Func<EnemyController, HealthController, AttackBehavior> parFunc)
: IAttackBehaviorCreator
{
public AttackBehavior Create(EnemyController parEnemyController, HealthController parHealthController)
{
return parFunc(parEnemyController, parHealthController);
}
}

View File

@@ -0,0 +1,8 @@
using DoomDeathmatch.Component.MVC.Controller;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public interface IAttackBehaviorCreator
{
public AttackBehavior Create(EnemyController parEnemyController, HealthController parHealthController);
}

View File

@@ -0,0 +1,26 @@
using DoomDeathmatch.Component.MVC.Controller;
using Engine.Scene;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
public class ObjectSpawnAttackBehavior(
EnemyController parEnemyController,
HealthController parHealthController,
float parCooldown,
Func<EnemyController, HealthController, GameObject> parObjectSpawnFunc
)
: CooldownAttackBehavior(parEnemyController, parHealthController, parCooldown)
{
protected override bool CanAttack()
{
return true;
}
protected override bool ActivateAttack()
{
var enemyObject = parObjectSpawnFunc(_enemyController, _healthController);
_enemyController.GameObject.Scene!.Add(enemyObject);
return true;
}
}

View File

@@ -0,0 +1,83 @@
using DoomDeathmatch.Component.MVC.Model.Enemy.Attack;
using DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
using DoomDeathmatch.Scene.Play;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy;
public class EnemyData
{
public static EnemyData Demon =>
new()
{
Id = "demon",
Name = "Демон",
Texture = "texture/demon.png",
BaseHealth = 150,
BaseScore = 50,
BaseSpeed = 8,
MovementBehavior = new FollowPlayerMovementBehavior(1.5f),
AttackBehaviorCreator = new FuncAttackBehaviorCreator(
(parEnemyController, parHealthController) =>
new CloseCooldownAttackBehavior(parEnemyController, parHealthController, 1.75f, 2.5f, 10)
)
};
public static EnemyData Imp =>
new()
{
Id = "imp",
Name = "Имп",
Texture = "texture/imp.png",
BaseHealth = 300,
BaseScore = 200,
BaseSpeed = 7,
MovementBehavior = new FollowPlayerMovementBehavior(10f),
AttackBehaviorCreator = new FuncAttackBehaviorCreator(
(parEnemyController, parHealthController) =>
new CompositeAttackBehavior(parEnemyController, parHealthController,
[
new CloseContinuousAttackBehavior(parEnemyController, parHealthController, 1.75f, 10f),
new ObjectSpawnAttackBehavior(parEnemyController, parHealthController, 4f,
(parEnemyController, parHealthController) =>
{
var direction =
(parHealthController.GameObject.Transform.Translation -
parEnemyController.GameObject.Transform.Translation).Normalized();
var fireballObject = EnemyObject.CreateImpFireball(parEnemyController.GameObject.Scene!,
parEnemyController.GameObject.Transform.Translation + new Vector3(0, 0f, 1.75f),
direction * 25,
35,
parHealthController.GameObject.Transform
);
return fireballObject;
}
)
]
)
)
};
public string Id { get; private init; } = "";
public string Name { get; private init; } = "";
public string Texture { get; private init; } = "";
public float BaseHealth { get; private init; }
public int BaseScore { get; private init; }
public float BaseSpeed { get; private init; }
public IMovementBehavior MovementBehavior { get; private init; }
public IAttackBehaviorCreator AttackBehaviorCreator { get; private init; }
private EnemyData() { }
public override bool Equals(object? parObj)
{
return parObj is EnemyData enemyData && Id == enemyData.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}

View File

@@ -0,0 +1,12 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
public class FollowPlayerMovementBehavior(float parRadius) : IMovementBehavior
{
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition)
{
var direction = (parPosition - parPlayerPosition).Normalized();
return parPlayerPosition + parRadius * direction;
}
}

View File

@@ -0,0 +1,8 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
public interface IMovementBehavior
{
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition);
}

View File

@@ -0,0 +1,11 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
public class StandingMovementBehavior : IMovementBehavior
{
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition)
{
return parPosition;
}
}

View File

@@ -8,6 +8,7 @@ public class HealthModel
set
{
_maxHealth = Math.Max(value, 1);
_health = Math.Clamp(_health, 0, _maxHealth);
HealthChanged?.Invoke(this);
}
}
@@ -17,7 +18,11 @@ public class HealthModel
get => _health;
set
{
_health = Math.Clamp(value, 0, MaxHealth);
value = Math.Clamp(value, 0, MaxHealth);
if (Math.Abs(_health - value) < float.Epsilon)
return;
_health = value;
HealthChanged?.Invoke(this);
}
}

View File

@@ -0,0 +1,8 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
public interface IShootPattern
{
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight);
}

View File

@@ -0,0 +1,11 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
public class LineShootPattern : IShootPattern
{
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight)
{
return [Vector3.Zero];
}
}

View File

@@ -0,0 +1,21 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
public class RandomFlatSpreadShootPattern(float parAngle, uint parCount) : IShootPattern
{
private readonly Random _random = new();
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight)
{
for (var i = 0; i < parCount; i++)
{
var angle = parAngle * ((float)_random.NextDouble() * 2 - 1);
var delta = MathF.Tan(angle);
var offset = parRight * delta;
yield return offset;
}
}
}

View File

@@ -1,18 +1,35 @@
namespace DoomDeathmatch.Component.MVC.Model;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
public class WeaponData
{
public static WeaponData Pistol =>
new(30) { Id = "pistol", Name = "Пистолет", Texture = "texture/pistol.png", Damage = 10 };
new(30)
{
Id = "pistol",
Name = "Пистолет",
Texture = "texture/pistol.png",
Damage = 30,
ShootPattern = new LineShootPattern()
};
public static WeaponData Shotgun =>
new(10) { Id = "shotgun", Name = "Дробовик", Texture = "texture/shotgun.png", Damage = 50 };
new(10)
{
Id = "shotgun",
Name = "Дробовик",
Texture = "texture/shotgun.png",
Damage = 5,
ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(20), 40)
};
public string Id { get; private init; } = "";
public string Name { get; private init; } = "";
public string Texture { get; private init; } = "";
public int Damage { get; private init; }
public int MaxAmmo { get; }
public IShootPattern ShootPattern { get; private init; }
public int Ammo
{

View File

@@ -1,4 +1,6 @@
namespace DoomDeathmatch.Component.MVC.Model;
using DoomDeathmatch.Component.MVC.Model.Weapon;
namespace DoomDeathmatch.Component.MVC.Model;
public class WeaponModel
{

View File

@@ -0,0 +1,16 @@
using DoomDeathmatch.Component.MVC.Model;
using Engine.Scene.Component.BuiltIn.Renderer;
namespace DoomDeathmatch.Component.MVC.View;
public class EnemyHealthView : HealthView
{
public EnemyHealthView(TextRenderer parHealthTextRenderer) : base(parHealthTextRenderer)
{
}
public override void UpdateView(HealthModel parHealthModel)
{
_healthTextRenderer.Text = $"Здоровье: {parHealthModel.Health:000}";
}
}

View File

@@ -0,0 +1,20 @@
using DoomDeathmatch.Component.MVC.Model.Enemy;
using Engine.Graphics.Texture;
using Engine.Scene.Component.BuiltIn.Renderer;
namespace DoomDeathmatch.Component.MVC.View;
public class EnemyView : Engine.Scene.Component.Component
{
private readonly Box2DRenderer _box2DRenderer;
public EnemyView(Box2DRenderer parBox2DRenderer)
{
_box2DRenderer = parBox2DRenderer;
}
public void UpdateView(EnemyData parEnemyData)
{
_box2DRenderer.Texture = Engine.Engine.Instance.AssetResourceManager.Load<Texture>(parEnemyData.Texture);
}
}

View File

@@ -5,14 +5,14 @@ namespace DoomDeathmatch.Component.MVC.View;
public class HealthView : Engine.Scene.Component.Component
{
private readonly TextRenderer _healthTextRenderer;
protected readonly TextRenderer _healthTextRenderer;
public HealthView(TextRenderer parHealthTextRenderer)
{
_healthTextRenderer = parHealthTextRenderer;
}
public void UpdateView(HealthModel parHealthModel)
public virtual void UpdateView(HealthModel parHealthModel)
{
var percentage = parHealthModel.Health / parHealthModel.MaxHealth * 100;
if (parHealthModel.Health != 0)

View File

@@ -0,0 +1,21 @@
using Engine.Scene.Component.BuiltIn.Renderer;
namespace DoomDeathmatch.Component.MVC.View;
public class TimerView : Engine.Scene.Component.Component
{
private readonly TextRenderer _timerTextRenderer;
public TimerView(TextRenderer parTimerTextRenderer)
{
_timerTextRenderer = parTimerTextRenderer;
}
public void UpdateView(double parTime)
{
var seconds = Math.Floor(parTime) % 60;
var minutes = Math.Floor(parTime / 60) % 60;
_timerTextRenderer.Text = $"Время: {minutes:00}:{seconds:00}";
}
}

View File

@@ -1,4 +1,5 @@
using DoomDeathmatch.Component.MVC.Model;
using DoomDeathmatch.Component.MVC.Model.Weapon;
using Engine.Graphics.Texture;
using Engine.Scene.Component.BuiltIn.Renderer;

View File

@@ -0,0 +1,26 @@
using Engine.Scene;
namespace DoomDeathmatch.Component.UI;
public class MenuControllerComponent : Engine.Scene.Component.Component
{
private readonly Dictionary<string, GameObject> _menuItems = new();
public void AddMenuItem(string parName, GameObject parGameObject)
{
_menuItems.Add(parName, parGameObject);
}
public void RemoveMenuItem(string parName)
{
_menuItems.Remove(parName);
}
public void SelectMenuItem(string parName)
{
foreach (var (name, menuItem) in _menuItems)
{
menuItem.IsEnabled = name == parName;
}
}
}

View File

@@ -15,10 +15,11 @@ public class BillboardComponent : Engine.Scene.Component.Component
return;
}
var targetPosition = Target.TransformMatrix.ExtractTranslation();
var currentPosition = GameObject.Transform.Translation;
var targetPosition = Target.GetFullTranslation();
var currentPosition = GameObject.Transform.GetFullTranslation();
var forward = targetPosition - currentPosition;
forward -= Vector3.Dot(forward, Up) * Up;
if (forward.LengthSquared > 0)
forward.Normalize();
@@ -26,7 +27,7 @@ public class BillboardComponent : Engine.Scene.Component.Component
if (right.LengthSquared > 0)
right.Normalize();
var recalculatedUp = Vector3.Cross(forward, right);
var recalculatedUp = Vector3.Cross(forward, right).Normalized();
var rotationMatrix = new Matrix3(
right.X, recalculatedUp.X, forward.X,
@@ -34,8 +35,6 @@ public class BillboardComponent : Engine.Scene.Component.Component
right.Z, recalculatedUp.Z, forward.Z
);
var rotation = Quaternion.FromMatrix(rotationMatrix);
GameObject.Transform.Rotation = rotation;
GameObject.Transform.Rotation = Quaternion.FromMatrix(rotationMatrix);
}
}

View File

@@ -0,0 +1,53 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
public class AABBCollider
{
public Vector3 Size { get; set; }
public Vector3 Position { get; set; }
public Vector3 Max => Position + Size / 2;
public Vector3 Min => Position - Size / 2;
public bool Intersects(AABBCollider parCollider)
{
var max = Max;
var min = Min;
var otherMax = parCollider.Max;
var otherMin = parCollider.Min;
return max.X >= otherMin.X && min.X <= otherMax.X && max.Y >= otherMin.Y && min.Y <= otherMax.Y &&
max.Z >= otherMin.Z && min.Z <= otherMax.Z;
}
public Vector3 GetCollisionNormal(AABBCollider parOther)
{
var normal = Vector3.Zero;
var diff = parOther.Position - Position;
// Calculate penetration depths for each axis
var penX = (Size.X / 2 + parOther.Size.X / 2) - Math.Abs(diff.X);
var penY = (Size.Y / 2 + parOther.Size.Y / 2) - Math.Abs(diff.Y);
var penZ = (Size.Z / 2 + parOther.Size.Z / 2) - Math.Abs(diff.Z);
// Use the axis with the smallest penetration
if (penX < penY && penX < penZ)
{
var sign = Math.Sign(diff.X);
normal.X = sign == 0 ? 1 : sign;
}
else if (penY < penX && penY < penZ)
{
var sign = Math.Sign(diff.Y);
normal.Y = sign == 0 ? 1 : sign;
}
else
{
var sign = Math.Sign(diff.Z);
normal.Z = sign == 0 ? 1 : sign;
}
return normal.Normalized();
}
}

View File

@@ -0,0 +1,31 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
public class AABBColliderComponent : Engine.Scene.Component.Component
{
public event Action<AABBColliderComponent>? OnCollision;
public AABBCollider Collider { get; set; } = new();
public Vector3 Offset { get; set; } = Vector3.Zero;
public ISet<string> ColliderGroups => _colliderGroups;
public ISet<string> ExcludeColliderCollideGroups => _excludeColliderCollideGroups;
private readonly HashSet<string> _colliderGroups = ["default"];
private readonly HashSet<string> _excludeColliderCollideGroups = [];
public override void Update(double parDeltaTime)
{
Collider.Position = GameObject.Transform.GetFullTranslation() + Offset;
}
public void CollideWith(AABBColliderComponent parCollider)
{
OnCollision?.Invoke(parCollider);
}
public bool InColliderGroup(string parGroup)
{
return ColliderGroups.Contains(parGroup);
}
}

View File

@@ -0,0 +1,28 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
public class ColliderForceFieldComponent : Engine.Scene.Component.Component
{
private AABBColliderComponent _collider = null!;
public override void Awake()
{
_collider = GameObject.GetComponent<AABBColliderComponent>()!;
_collider.OnCollision += OnCollision;
}
private void OnCollision(AABBColliderComponent parCollider)
{
var rigidbody = parCollider.GameObject.GetComponent<RigidbodyComponent>();
if (rigidbody == null)
return;
var normal = _collider.Collider.GetCollisionNormal(parCollider.Collider);
var speedAlongNormal = Vector3.Dot(rigidbody.Velocity, normal);
if (speedAlongNormal >= 0)
return;
rigidbody.AddVelocity(-normal * speedAlongNormal);
}
}

View File

@@ -0,0 +1,164 @@
using System.Diagnostics.CodeAnalysis;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
public class CollisionManager : Engine.Scene.Component.Component
{
private List<AABBColliderComponent> _colliders = [];
public override void PreUpdate()
{
_colliders = GameObject.Scene!.FindAllComponents<AABBColliderComponent>();
}
public override void Update(double parDeltaTime)
{
for (var i = 0; i < _colliders.Count; i++)
{
var colliderA = _colliders[i];
for (var j = i + 1; j < _colliders.Count; j++)
{
var colliderB = _colliders[j];
var canCollideAB = colliderA.ExcludeColliderCollideGroups.Count == 0 ||
!colliderA.ExcludeColliderCollideGroups.IsSubsetOf(colliderB.ColliderGroups);
var canCollideBA = colliderB.ExcludeColliderCollideGroups.Count == 0 ||
!colliderB.ExcludeColliderCollideGroups.IsSubsetOf(colliderA.ColliderGroups);
if (!canCollideAB && !canCollideBA)
continue;
if (!colliderA.Collider.Intersects(colliderB.Collider))
continue;
if (canCollideAB)
colliderA.CollideWith(colliderB);
if (canCollideBA)
colliderB.CollideWith(colliderA);
}
}
}
public bool Raycast(Vector3 parStart, Vector3 parDirection, HashSet<string> parColliderGroups,
[MaybeNullWhen(false)] out RaycastResult parResult)
{
var start = parStart;
var direction = parDirection.Normalized();
var closestDistance = float.MaxValue;
parResult = null;
foreach (var collider in _colliders)
{
if (!collider.ColliderGroups.Overlaps(parColliderGroups))
continue;
if (!RaycastAABB(start, direction, collider.Collider, out var hitPoint, out var normal))
continue;
var distance = (start - hitPoint).Length;
if (distance > closestDistance)
continue;
closestDistance = distance;
parResult = new RaycastResult
{
HitPoint = hitPoint, Normal = normal, Distance = distance, HitObject = collider.GameObject
};
}
return parResult != null;
}
private static bool RaycastAABB(Vector3 parOrigin, Vector3 parDirection, AABBCollider parCollider,
out Vector3 parHitPoint,
out Vector3 parHitNormal)
{
const int RIGHT = 0;
const int LEFT = 1;
const int MIDDLE = 2;
parHitPoint = Vector3.Zero;
parHitNormal = Vector3.Zero;
var minB = parCollider.Min;
var maxB = parCollider.Max;
// Initialize arrays to store the 3D components
var inside = true;
var quadrant = new int[3];
var maxT = new float[3];
var candidatePlane = new float[3];
// Find candidate planes
for (var i = 0; i < 3; i++)
{
if (parOrigin[i] < minB[i])
{
quadrant[i] = LEFT;
candidatePlane[i] = minB[i];
inside = false;
}
else if (parOrigin[i] > maxB[i])
{
quadrant[i] = RIGHT;
candidatePlane[i] = maxB[i];
inside = false;
}
else
{
quadrant[i] = MIDDLE;
}
}
// Ray origin inside bounding box
if (inside)
{
parHitPoint = parOrigin;
return true;
}
// Calculate T distances to candidate planes
for (var i = 0; i < 3; i++)
{
if (quadrant[i] != MIDDLE && parDirection[i] != 0.0f)
maxT[i] = (candidatePlane[i] - parOrigin[i]) / parDirection[i];
else
maxT[i] = -1.0f;
}
// Get largest of the maxT's for final choice of intersection
var whichPlane = 0;
for (var i = 1; i < 3; i++)
{
if (maxT[whichPlane] < maxT[i])
whichPlane = i;
}
// Check final candidate actually inside box
if (maxT[whichPlane] < 0.0f)
return false;
for (var i = 0; i < 3; i++)
{
if (whichPlane != i)
{
parHitPoint[i] = parOrigin[i] + maxT[whichPlane] * parDirection[i];
if (parHitPoint[i] < minB[i] || parHitPoint[i] > maxB[i])
return false;
}
else
{
parHitPoint[i] = candidatePlane[i];
// Calculate normal for the intersection plane
parHitNormal[i] = (quadrant[i] == LEFT) ? -1.0f : 1.0f;
}
}
return true;
}
}

View File

@@ -0,0 +1,12 @@
using Engine.Scene;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
public class RaycastResult
{
public float Distance { get; init; }
public Vector3 HitPoint { get; init; }
public Vector3 Normal { get; init; }
public GameObject HitObject { get; init; }
}

View File

@@ -1,19 +1,21 @@
using Engine.Input;
using DoomDeathmatch.Component.MVC.Controller;
using Engine.Input;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util;
public class ControllerComponent : Engine.Scene.Component.Component
{
public float Speed { get; set; } = 10.0f;
public float RotationSpeed { get; set; } = 70.0f;
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
private RigidbodyComponent _rigidbody;
private MovementController _movementController = null!;
public override void Awake()
{
_rigidbody = GameObject.GetComponent<RigidbodyComponent>()!;
_movementController = GameObject.GetComponent<MovementController>()!;
ArgumentNullException.ThrowIfNull(_movementController);
}
public override void Update(double parDeltaTime)
@@ -40,14 +42,7 @@ public class ControllerComponent : Engine.Scene.Component.Component
movement.Normalize();
movement = GameObject.Transform.Rotation * movement;
_rigidbody.AddVelocity(Speed * movement);
}
var velocityXy = _rigidbody.Velocity.Xy;
if (velocityXy.LengthSquared > Speed * Speed)
{
var length = velocityXy.Length;
_rigidbody.AddVelocity(new Vector3(-(length - Speed) / length * velocityXy));
_movementController.ApplyMovement(movement);
}
GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(rotation) *

View File

@@ -7,11 +7,13 @@ public class DragComponent : Engine.Scene.Component.Component
public float Drag { get; set; } = 1f;
public Vector3 Coefficient { get; set; } = Vector3.One;
private RigidbodyComponent _rigidbody;
private RigidbodyComponent _rigidbody = null!;
public override void Awake()
{
_rigidbody = GameObject.GetComponent<RigidbodyComponent>()!;
ArgumentNullException.ThrowIfNull(_rigidbody);
}
public override void Update(double parDeltaTime)

View File

@@ -0,0 +1,26 @@
using DoomDeathmatch.Component.MVC.Controller;
using DoomDeathmatch.Component.Util.Collision;
namespace DoomDeathmatch.Component.Util;
public class FireballComponent : Engine.Scene.Component.Component
{
public float Damage { get; set; }
public override void Awake()
{
var collider = GameObject.GetComponent<AABBColliderComponent>()!;
ArgumentNullException.ThrowIfNull(collider);
collider.OnCollision += OnCollision;
}
private void OnCollision(AABBColliderComponent parCollider)
{
var healthController = parCollider.GameObject.GetComponent<HealthController>();
healthController?.TakeDamage(Damage);
GameObject.Scene!.Remove(GameObject);
}
}

View File

@@ -16,12 +16,14 @@ public class GravityComponent : Engine.Scene.Component.Component
public bool IsInAir { get; private set; } = false;
private RigidbodyComponent _rigidbody;
private RigidbodyComponent _rigidbody = null!;
private Vector3 _direction = -Vector3.UnitZ;
public override void Awake()
{
_rigidbody = GameObject.GetComponent<RigidbodyComponent>()!;
ArgumentNullException.ThrowIfNull(_rigidbody);
}
public override void Update(double parDeltaTime)

View File

@@ -1,22 +0,0 @@
using Engine.Input;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util;
public class RotateComponent : Engine.Scene.Component.Component
{
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
public override void Update(double parDeltaTime)
{
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Q))
GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, (float)parDeltaTime * 0.5f);
else if (_inputHandler.IsKeyPressed(KeyboardButtonCode.E))
GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, -(float)parDeltaTime * 0.5f);
if (_inputHandler.IsMouseButtonPressed(MouseButtonCode.Left))
GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, (float)parDeltaTime * 0.5f);
else if (_inputHandler.IsMouseButtonPressed(MouseButtonCode.Right))
GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, -(float)parDeltaTime * 0.5f);
}
}

View File

@@ -1,19 +1,4 @@
using System.Text.Json;
using DoomDeathmatch.Component;
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Scene.Main;
using DoomDeathmatch.Scene.Rules;
using Engine.Asset.Font;
using Engine.Asset.Font.Metadata;
using Engine.Asset.Mesh;
using Engine.Asset.Mesh.Loader;
using Engine.Graphics.Camera;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Texture;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
using DoomDeathmatch.Scene;
namespace DoomDeathmatch;

View File

@@ -1,10 +1,31 @@
using Engine.Scene;
using DoomDeathmatch.Component.Util.Collision;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
namespace DoomDeathmatch;
public static class GameObjectUtil
{
public static GameObject CreateGameObject(Engine.Scene.Scene parScene)
{
var gameObject = new GameObject();
parScene.Add(gameObject);
return gameObject;
}
public static GameObject CreateGameObject(Engine.Scene.Scene parScene, Transform parTransform)
{
var gameObject = new GameObject(parTransform);
parScene.Add(gameObject);
return gameObject;
}
public static GameObject CreateGameObject(Engine.Scene.Scene parScene,
List<Engine.Scene.Component.Component> parComponents)
{
@@ -32,4 +53,92 @@ public static class GameObjectUtil
return gameObject;
}
public static GameObject CreateColliderForceField(Engine.Scene.Scene parScene, AABBColliderComponent parCollider)
{
var offset = parCollider.Offset;
parCollider.Offset = Vector3.Zero;
var forceFieldObject = CreateGameObject(parScene,
new Transform { Translation = offset, Size = parCollider.Collider.Size },
[
parCollider,
new ColliderForceFieldComponent()
]
);
// var color = new Vector4(1, 0, 0, 0.5f);
// var size = parCollider.Collider.Size;
//
// var frontPlaneObject = CreateGameObject(parScene,
// new Transform
// {
// Translation = new Vector3(0, -size.Y / 2, 0),
// Size = new Vector3(size.X, size.Z, 0),
// Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathF.PI / 2)
// },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// var backPlaneObject = CreateGameObject(parScene,
// new Transform
// {
// Translation = new Vector3(0, size.Y / 2, 0),
// Size = new Vector3(size.X, size.Z, 0),
// Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, -MathF.PI / 2)
// },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// var leftPlaneObject = CreateGameObject(parScene,
// new Transform
// {
// Translation = new Vector3(-size.X / 2, 0, 0),
// Size = new Vector3(size.Z, size.Y, 0),
// Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, MathF.PI / 2)
// },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// var rightPlaneObject = CreateGameObject(parScene,
// new Transform
// {
// Translation = new Vector3(size.X / 2, 0, 0),
// Size = new Vector3(size.Z, size.Y, 0),
// Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, -MathF.PI / 2)
// },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// var topPlaneObject = CreateGameObject(parScene,
// new Transform { Translation = new Vector3(0, 0, size.Z / 2), Size = new Vector3(size.X, size.Y, 0) },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// var bottomPlaneObject = CreateGameObject(parScene,
// new Transform { Translation = new Vector3(0, 0, -size.Z / 2), Size = new Vector3(size.X, size.Y, 0) },
// [
// new Box2DRenderer { Color = color }
// ]
// );
//
// parScene.AddChild(forceFieldObject, frontPlaneObject);
// parScene.AddChild(forceFieldObject, backPlaneObject);
// parScene.AddChild(forceFieldObject, leftPlaneObject);
// parScene.AddChild(forceFieldObject, rightPlaneObject);
// parScene.AddChild(forceFieldObject, topPlaneObject);
// parScene.AddChild(forceFieldObject, bottomPlaneObject);
return forceFieldObject;
}
}

View File

@@ -1,41 +0,0 @@
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Scene.Main;
using Engine.Asset.Font;
using Engine.Input;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Scene.Leaders;
public static class LeadersScene
{
public static Engine.Scene.Scene Create(Engine.Engine parEngine)
{
var scene = new Engine.Scene.Scene();
var (cameraObject, camera) = UiUtil.CreateOrthographicCamera(scene);
var (uiContainerObject, uiContainer) = UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera });
var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, scene, uiContainer);
logoUi.Offset = new Vector2(0, 3f);
var (backUiObject, backUi, _) = UiUtil.CreateTextUi(scene, uiContainer,
UiUtil.GetDoomFont(parEngine), "Назад");
backUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine));
var (stackObject, stack) = UiUtil.CreateStackUi(scene,
new StackComponent { Offset = new Vector2(0, -1f), Container = uiContainer, Children = { backUi } });
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(scene, new SelectorComponent { Children = { backUi } });
scene.AddChild(uiContainerObject, selectorObject);
scene.AddChild(uiContainerObject, logoObject);
scene.AddChild(uiContainerObject, stackObject);
scene.AddChild(stackObject, backUiObject);
return scene;
}
}

View File

@@ -1,63 +0,0 @@
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Scene.Leaders;
using DoomDeathmatch.Scene.Play;
using DoomDeathmatch.Scene.Rules;
using Engine.Scene.Component.BuiltIn;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Scene.Main;
public static class MainScene
{
public static Engine.Scene.Scene Create(Engine.Engine parEngine)
{
var scene = new Engine.Scene.Scene();
var (cameraObject, camera) = UiUtil.CreateOrthographicCamera(scene);
var (uiContainerObject, uiContainer) =
UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera });
var (playUiObject, playUi, _) =
UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(parEngine), "Играть");
var (leadersUiObject, leadersUi, _) =
UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(parEngine), "Лидеры");
var (rulesUiObject, rulesUi, _) =
UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(parEngine), "Правила");
var (exitUiObject, exitUi, _) =
UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(parEngine), "Выход");
var (stackObject, stack) = UiUtil.CreateStackUi(scene,
new StackComponent
{
Offset = new Vector2(0, -1f), Container = uiContainer, Children = { playUi, leadersUi, rulesUi, exitUi }
});
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
playUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => PlayScene.Create(parEngine));
leadersUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => LeadersScene.Create(parEngine));
rulesUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => RulesScene.Create(parEngine));
exitUi.OnClick += _ => parEngine.Close();
var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, scene, uiContainer);
logoUi.Offset = new Vector2(0, 3f);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(scene,
new SelectorComponent { Children = { playUi, leadersUi, rulesUi, exitUi } });
scene.AddChild(uiContainerObject, selectorObject);
scene.AddChild(uiContainerObject, stackObject);
scene.AddChild(uiContainerObject, logoObject);
scene.AddChild(stackObject, playUiObject);
scene.AddChild(stackObject, leadersUiObject);
scene.AddChild(stackObject, rulesUiObject);
scene.AddChild(stackObject, exitUiObject);
return scene;
}
}

View File

@@ -0,0 +1,138 @@
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Scene.Play;
using Engine.Scene;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Scene;
public static class MainScene
{
public static Engine.Scene.Scene Create(Engine.Engine parEngine)
{
var scene = new Engine.Scene.Scene();
var (cameraObject, camera) = UiUtil.CreateOrthographicCamera(scene);
var (uiContainerObject, uiContainer) =
UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera });
var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, scene, uiContainer);
logoUi.Offset = new Vector2(0, 3f);
scene.AddChild(uiContainerObject, logoObject);
var menuController = new MenuControllerComponent();
var menuControllerObject = GameObjectUtil.CreateGameObject(scene, [
menuController
]);
var mainMenu = CreateMainMenu(parEngine, scene, uiContainer, menuController);
menuController.AddMenuItem("main", mainMenu);
var leadersMenu = CreateLeadersMenu(parEngine, scene, uiContainer, menuController);
leadersMenu.IsEnabled = false;
menuController.AddMenuItem("leaders", leadersMenu);
var rulesMenu = CreateRulesMenu(parEngine, scene, uiContainer, menuController);
rulesMenu.IsEnabled = false;
menuController.AddMenuItem("rules", rulesMenu);
scene.AddChild(uiContainerObject, mainMenu);
scene.AddChild(uiContainerObject, leadersMenu);
scene.AddChild(uiContainerObject, rulesMenu);
return scene;
}
private static GameObject CreateMainMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene,
UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
{
var parentObject = GameObjectUtil.CreateGameObject(parScene);
var (playUiObject, playUi, _) =
UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Играть");
var (leadersUiObject, leadersUi, _) =
UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Лидеры");
var (rulesUiObject, rulesUi, _) =
UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Правила");
var (exitUiObject, exitUi, _) =
UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Выход");
var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
new StackComponent
{
Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { playUi, leadersUi, rulesUi, exitUi }
});
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
playUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => PlayScene.Create(parEngine));
leadersUi.OnClick += _ => parMenuController.SelectMenuItem("leaders");
rulesUi.OnClick += _ => parMenuController.SelectMenuItem("rules");
exitUi.OnClick += _ => parEngine.Close();
var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene,
new SelectorComponent { Children = { playUi, leadersUi, rulesUi, exitUi } });
parScene.AddChild(parentObject, selectorObject);
parScene.AddChild(parentObject, stackObject);
parScene.AddChild(stackObject, playUiObject);
parScene.AddChild(stackObject, leadersUiObject);
parScene.AddChild(stackObject, rulesUiObject);
parScene.AddChild(stackObject, exitUiObject);
return parentObject;
}
private static GameObject CreateLeadersMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene,
UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
{
var parentObject = GameObjectUtil.CreateGameObject(parScene);
var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer,
UiUtil.GetDoomFont(parEngine), "Назад");
backUi.OnClick += _ => parMenuController.SelectMenuItem("main");
var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
new StackComponent { Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { backUi } });
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene, new SelectorComponent { Children = { backUi } });
parScene.AddChild(parentObject, selectorObject);
parScene.AddChild(parentObject, stackObject);
parScene.AddChild(stackObject, backUiObject);
return parentObject;
}
private static GameObject CreateRulesMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene,
UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
{
var parentObject = GameObjectUtil.CreateGameObject(parScene);
var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer,
UiUtil.GetDoomFont(parEngine), "Назад");
backUi.OnClick += _ => parMenuController.SelectMenuItem("main");
var (rulesObject, rulesUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer,
UiUtil.GetDoomFont(parEngine), "Правила");
var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
new StackComponent { Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { rulesUi, backUi } });
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene, new SelectorComponent { Children = { backUi } });
parScene.AddChild(parentObject, selectorObject);
parScene.AddChild(parentObject, stackObject);
parScene.AddChild(stackObject, rulesObject);
parScene.AddChild(stackObject, backUiObject);
return parentObject;
}
}

View File

@@ -0,0 +1,90 @@
using DoomDeathmatch.Component.MVC.Controller;
using DoomDeathmatch.Component.MVC.Model.Enemy;
using DoomDeathmatch.Component.MVC.View;
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Component.Util;
using DoomDeathmatch.Component.Util.Collision;
using Engine.Graphics.Texture;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Scene.Play;
public static class EnemyObject
{
public static GameObject CreateEnemy(Engine.Engine parEngine, Engine.Scene.Scene parScene, EnemyData parEnemyData)
{
var enemyHealthTextRenderer = new TextRenderer { Font = UiUtil.GetDoomFont(parEngine), Text = "Здоровье: 000" };
var displayBox2DRenderer = new Box2DRenderer();
var demonObject = GameObjectUtil.CreateGameObject(parScene,
[
new EnemyController(parEnemyData),
new EnemyView(displayBox2DRenderer),
new HealthController(parEnemyData.BaseHealth),
new EnemyHealthView(enemyHealthTextRenderer),
new MovementController(),
new RigidbodyComponent(),
new DragComponent { Drag = 5f, Coefficient = new Vector3(1, 1, 0) },
new AABBColliderComponent
{
Collider = new AABBCollider { Size = new Vector3(1, 1, 2) },
Offset = new Vector3(0, 0f, 1f),
ColliderGroups = { "enemy" },
ExcludeColliderCollideGroups = { "enemy" }
},
]);
var demonHealthContainer = GameObjectUtil.CreateGameObject(parScene,
new Transform { Translation = new Vector3(0, 1.25f, 0f), Scale = new Vector3(0.2f) }
);
var demonHealthObject = GameObjectUtil.CreateGameObject(parScene, [
enemyHealthTextRenderer,
new TextAlignComponent { Alignment = TextAlignComponent.Align.Center }
]
);
var demonVisualObject = GameObjectUtil.CreateGameObject(parScene,
new Transform { Translation = new Vector3(0, 0f, 1f), Size = new Vector3(1, 2, 1) }, [
displayBox2DRenderer,
new BillboardComponent(),
]
);
parScene.AddChild(demonObject, demonVisualObject);
parScene.AddChild(demonVisualObject, demonHealthContainer);
parScene.AddChild(demonHealthContainer, demonHealthObject);
return demonObject;
}
public static GameObject CreateImpFireball(Engine.Scene.Scene parScene, Vector3 parPosition,
Vector3 parVelocity, float parDamage, Transform? parBillboardTarget = null)
{
var rigidbodyComponent = new RigidbodyComponent();
rigidbodyComponent.AddVelocity(parVelocity);
var fireballObject = GameObjectUtil.CreateGameObject(parScene,
new Transform { Translation = parPosition, Size = new Vector3(0.5f) },
[
rigidbodyComponent,
new Box2DRenderer
{
Texture = Engine.Engine.Instance.AssetResourceManager.Load<Texture>("texture/fireball.png")
},
new BillboardComponent { Target = parBillboardTarget },
new FireballComponent { Damage = parDamage },
new AABBColliderComponent
{
Collider = new AABBCollider { Size = new Vector3(0.25f) }, ExcludeColliderCollideGroups = { "enemy" }
}
]
);
return fireballObject;
}
}

View File

@@ -1,13 +1,13 @@
using DoomDeathmatch.Component;
using DoomDeathmatch.Component.MVC.Controller;
using DoomDeathmatch.Component.MVC.Controller;
using DoomDeathmatch.Component.MVC.Model.Enemy;
using DoomDeathmatch.Component.MVC.View;
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Component.Util;
using DoomDeathmatch.Component.Util.Collision;
using Engine.Asset.Mesh;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Texture;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;
@@ -23,8 +23,141 @@ public static class PlayScene
var (uiContainerObject, uiContainer) =
UiUtil.CreateContainerUi(scene, new UiContainerComponent { Camera = hudCamera });
var menuController = new MenuControllerComponent();
var menuControllerObject = GameObjectUtil.CreateGameObject(scene, [
menuController
]);
var (playUiObject, (playWeaponView, playHealthView, playScoreView, plaTimerView)) =
CreateGameUi(parEngine, scene, uiContainer, menuController);
menuController.AddMenuItem("play", playUiObject);
scene.AddChild(uiContainerObject, playUiObject);
var escapeUiObject = CreateEscapeMenu(parEngine, scene, uiContainer, menuController);
escapeUiObject.IsEnabled = false;
menuController.AddMenuItem("escape", escapeUiObject);
scene.AddChild(uiContainerObject, escapeUiObject);
var (perspectiveCameraObject, perspectiveCamera) = UiUtil.CreatePerspectiveCamera(scene);
perspectiveCameraObject.Transform.Translation.Z = 2;
var playerObject = GameObjectUtil.CreateGameObject(scene, [
new ControllerComponent(),
new MovementController { Speed = 10f },
new RigidbodyComponent(),
new DragComponent { Drag = 10f, Coefficient = new Vector3(1, 1, 0) },
new AABBColliderComponent
{
Collider = new AABBCollider { Size = new Vector3(1, 1, 2) },
Offset = new Vector3(0, 0f, 1f),
ColliderGroups = { "player" },
ExcludeColliderCollideGroups = { "player" }
},
new PlayerController(perspectiveCamera),
new WeaponController(),
playWeaponView,
new HealthController(),
playHealthView,
]);
playerObject.Transform.Translation.X = -3;
var gameControllerObject = GameObjectUtil.CreateGameObject(scene, [
new GameController(menuController),
new TimerController(),
plaTimerView,
new ScoreController(),
playScoreView,
new CollisionManager(),
]);
scene.AddChild(playerObject, perspectiveCameraObject);
var mapObject = LoadMap(parEngine, scene, "default");
var impObject = EnemyObject.CreateEnemy(parEngine, scene, EnemyData.Imp);
return scene;
}
private static GameObject LoadMap(Engine.Engine parEngine, Engine.Scene.Scene parScene, string parMapName)
{
var mapObject = GameObjectUtil.CreateGameObject(parScene, [
new MeshRenderer
{
Mesh = parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/mesh.obj"),
Albedo = parEngine.AssetResourceManager.Load<Texture>($"map/{parMapName}/texture.png")
}
]);
var colliders = parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/colliders.obj");
var collidersObject = MeshToColliders(parScene, colliders);
var monsterSpawnPoints = MeshToSpawnPoints(
parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/monster_spawners.obj"));
var valuableSpawnPoints = MeshToSpawnPoints(
parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));
parScene.AddChild(mapObject, collidersObject);
return mapObject;
}
private static List<Vector3> MeshToSpawnPoints(Mesh parMesh)
{
return parMesh.Indices.Select(parIndex => parMesh.Vertices[(int)parIndex]._position).ToList();
}
private static GameObject MeshToColliders(Engine.Scene.Scene parScene, Mesh parMesh)
{
var allCollidersObject = GameObjectUtil.CreateGameObject(parScene);
if (parMesh.Indices.Count % (4 * 6) != 0)
{
throw new InvalidOperationException("Mesh is not an AABB collider");
}
for (var i = 0; i < parMesh.Indices.Count; i += 24)
{
var max = new Vector3(float.MinValue);
var min = new Vector3(float.MaxValue);
for (var j = 0; j < 24; j++)
{
var position = parMesh.Vertices[(int)parMesh.Indices[i + j]]._position;
max = Vector3.ComponentMax(max, position);
min = Vector3.ComponentMin(min, position);
}
var size = max - min;
var offset = min + size / 2;
var colliderObject = GameObjectUtil.CreateColliderForceField(
parScene,
new AABBColliderComponent
{
Collider = new AABBCollider { Size = size }, Offset = offset, ColliderGroups = { "wall" }
}
);
parScene.AddChild(allCollidersObject, colliderObject);
}
return allCollidersObject;
}
private static (GameObject, (WeaponView, HealthView, ScoreView, TimerView)) CreateGameUi(Engine.Engine parEngine,
Engine.Scene.Scene parScene,
UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
{
var parentObject = GameObjectUtil.CreateGameObject(parScene);
var (bottomContainerObject, bottomContainer) =
UiUtil.CreateContainerUi(scene, new UiContainerComponent { Container = uiContainer });
UiUtil.CreateContainerUi(parScene, new UiContainerComponent { Container = parUiContainer });
bottomContainer.Anchor = Anchor.BottomCenter;
bottomContainer.Center = Anchor.BottomCenter;
bottomContainerObject.AddComponent(new Box2DRenderer
@@ -33,104 +166,121 @@ public static class PlayScene
});
bottomContainerObject.AddComponent(new CopySizeComponent
{
Target = uiContainerObject.Transform, Coefficient = new Vector3(1, 0, 1)
Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1)
});
bottomContainerObject.Transform.Size.Y = 1.5f;
scene.AddChild(uiContainerObject, bottomContainerObject);
parScene.AddChild(parentObject, bottomContainerObject);
var (gunObject, (gunUi, gunSprite)) = UiUtil.CreateSpriteUi(scene, bottomContainer,
parEngine.AssetResourceManager.Load<Texture>("texture/pistol.png"), RenderLayer.HUD);
var (gunObject, (gunUi, gunSprite)) = UiUtil.CreateSpriteUi(parScene, bottomContainer,
null, RenderLayer.HUD);
gunObject.Transform.Scale = new Vector3(5);
gunUi.Anchor = Anchor.TopCenter;
gunUi.Center = Anchor.BottomCenter;
scene.AddChild(bottomContainerObject, gunObject);
gunUi.Offset = new Vector2(0, -0.05f);
parScene.AddChild(bottomContainerObject, gunObject);
var (healthObject, healthUi, (_, healthTextRenderer)) =
UiUtil.CreateTextUi(scene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Здоровье: 000",
UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Здоровье: 000",
TextAlignComponent.Align.Center,
RenderLayer.HUD);
healthObject.Transform.Scale = new Vector3(0.75f);
healthUi.Anchor = Anchor.CenterLeft;
healthUi.Center = Anchor.CenterLeft;
scene.AddChild(bottomContainerObject, healthObject);
parScene.AddChild(bottomContainerObject, healthObject);
var (ammoObject, ammoUi, (_, ammoTextRenderer)) =
UiUtil.CreateTextUi(scene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Патроны: 00/00",
UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Патроны: 00/00",
TextAlignComponent.Align.Center,
RenderLayer.HUD);
ammoObject.Transform.Scale = new Vector3(0.75f);
ammoUi.Anchor = Anchor.TopRight;
ammoUi.Center = Anchor.TopRight;
scene.AddChild(bottomContainerObject, ammoObject);
parScene.AddChild(bottomContainerObject, ammoObject);
var (weaponObject, weaponUi, (_, weaponTextRenderer)) =
UiUtil.CreateTextUi(scene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Оружие: ОРУЖИЕОР",
UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Оружие: ОРУЖИЕОР",
TextAlignComponent.Align.Center,
RenderLayer.HUD);
weaponObject.Transform.Scale = new Vector3(0.75f);
weaponUi.Anchor = Anchor.BottomRight;
weaponUi.Center = Anchor.BottomRight;
scene.AddChild(bottomContainerObject, weaponObject);
parScene.AddChild(bottomContainerObject, weaponObject);
var (topContainerObject, topContainer) =
UiUtil.CreateContainerUi(scene, new UiContainerComponent { Container = uiContainer });
UiUtil.CreateContainerUi(parScene, new UiContainerComponent { Container = parUiContainer });
topContainer.Anchor = Anchor.TopCenter;
topContainer.Center = Anchor.TopCenter;
topContainerObject.AddComponent(
new Box2DRenderer { Color = new Vector4(1, 0, 0, 1), RenderLayer = RenderLayer.HUD });
topContainerObject.AddComponent(new CopySizeComponent
{
Target = uiContainerObject.Transform, Coefficient = new Vector3(1, 0, 1)
Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1)
});
topContainerObject.Transform.Size.Y = 1f;
scene.AddChild(uiContainerObject, topContainerObject);
parScene.AddChild(parentObject, topContainerObject);
var (timerObject, timerUi, _) =
UiUtil.CreateTextUi(scene, topContainer, UiUtil.GetDoomFont(parEngine), "Время: 00:00",
var (timerObject, timerUi, (_, timerTextRenderer)) =
UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Время: 00:00",
parRenderLayer: RenderLayer.HUD);
timerUi.Anchor = Anchor.CenterLeft;
timerUi.Center = Anchor.CenterLeft;
scene.AddChild(topContainerObject, timerObject);
parScene.AddChild(topContainerObject, timerObject);
var (scoreObject, scoreUi, (_, scoreTextRenderer)) =
UiUtil.CreateTextUi(scene, topContainer, UiUtil.GetDoomFont(parEngine), "Счет: 00000",
UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Счет: 00000",
parRenderLayer: RenderLayer.HUD);
scoreUi.Anchor = Anchor.CenterRight;
scoreUi.Center = Anchor.CenterRight;
scene.AddChild(topContainerObject, scoreObject);
parScene.AddChild(topContainerObject, scoreObject);
var playerObject = GameObjectUtil.CreateGameObject(scene, [
new RigidbodyComponent(),
new ControllerComponent { Speed = 5f },
new DragComponent { Drag = 5f, Coefficient = new Vector3(1, 1, 0) },
var weaponView = new WeaponView(weaponTextRenderer, ammoTextRenderer, gunSprite);
var healthView = new HealthView(healthTextRenderer);
var scoreView = new ScoreView(scoreTextRenderer);
var timerView = new TimerView(timerTextRenderer);
new PlayerController(),
return (parentObject, (weaponView, healthView, scoreView, timerView));
}
new WeaponController(),
new WeaponView(weaponTextRenderer, ammoTextRenderer, gunSprite),
private static GameObject CreateEscapeMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene,
UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
{
var parentObject = GameObjectUtil.CreateGameObject(parScene);
new HealthController(),
new HealthView(healthTextRenderer),
var (backgroundObject, backgroundUiContainer) = UiUtil.CreateBackgroundUi(parScene,
new UiContainerComponent { Container = parUiContainer }, new Vector4(0, 0, 0, 0.9f), RenderLayer.HUD);
backgroundObject.AddComponent(new CopySizeComponent { Target = parUiContainer.GameObject.Transform });
parScene.AddChild(parentObject, backgroundObject);
new ScoreController(),
new ScoreView(scoreTextRenderer),
]);
var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, parScene, backgroundUiContainer, RenderLayer.HUD);
logoUi.Offset = new Vector2(0, 3f);
parScene.AddChild(parentObject, logoObject);
var (perspectiveCameraObject, perspectiveCamera) = UiUtil.CreatePerspectiveCamera(scene);
perspectiveCameraObject.Transform.Translation.Z = 2;
var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer,
UiUtil.GetDoomFont(parEngine), "Назад",
parRenderLayer: RenderLayer.HUD);
backUi.OnClick += parUiComponent =>
parUiComponent.GameObject.Scene!.FindFirstComponent<GameController>()!.Unpause();
var mapObject = GameObjectUtil.CreateGameObject(scene, [
new MeshRenderer { Mesh = parEngine.AssetResourceManager.Load<Mesh>("model/map.obj") },
]);
var (exitUiObject, exitUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer,
UiUtil.GetDoomFont(parEngine), "Выход",
parRenderLayer: RenderLayer.HUD);
exitUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine));
var impObject = GameObjectUtil.CreateGameObject(scene,
new Transform { Translation = new Vector3(0, 0, 1), Scale = new Vector3(1, 2, 1), }, [
new Box2DRenderer { Texture = parEngine.AssetResourceManager.Load<Texture>("texture/imp.png") },
new BillboardComponent { Target = perspectiveCameraObject.Transform }
]);
var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
new StackComponent
{
Offset = new Vector2(0, -1f), Container = backgroundUiContainer, Children = { backUi, exitUi }
});
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
scene.AddChild(playerObject, perspectiveCameraObject);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene,
new SelectorComponent { Children = { backUi, exitUi } }, RenderLayer.HUD);
return scene;
parScene.AddChild(parentObject, selectorObject);
parScene.AddChild(parentObject, stackObject);
parScene.AddChild(stackObject, backUiObject);
parScene.AddChild(stackObject, exitUiObject);
return parentObject;
}
}

View File

@@ -1,43 +0,0 @@
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Scene.Main;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Scene.Rules;
public static class RulesScene
{
public static Engine.Scene.Scene Create(Engine.Engine parEngine)
{
var scene = new Engine.Scene.Scene();
var (cameraObject, camera) = UiUtil.CreateOrthographicCamera(scene);
var (uiContainerObject, uiContainer) = UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera });
var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, scene, uiContainer);
logoUi.Offset = new Vector2(0, 3f);
var (backUiObject, backUi, _) = UiUtil.CreateTextUi(scene, uiContainer,
UiUtil.GetDoomFont(parEngine), "Назад");
backUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine));
var (rulesObject, rulesUi, _) = UiUtil.CreateTextUi(scene, uiContainer,
UiUtil.GetDoomFont(parEngine), "Правила");
var (stackObject, stack) = UiUtil.CreateStackUi(scene,
new StackComponent { Offset = new Vector2(0, -1f), Container = uiContainer, Children = { rulesUi, backUi } });
stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
var (selectorObject, selector) = UiUtil.CreateSelectorUi(scene, new SelectorComponent { Children = { backUi } });
scene.AddChild(uiContainerObject, selectorObject);
scene.AddChild(uiContainerObject, logoObject);
scene.AddChild(uiContainerObject, stackObject);
scene.AddChild(stackObject, rulesObject);
scene.AddChild(stackObject, backUiObject);
return scene;
}
}

View File

@@ -155,7 +155,7 @@ public static class UiUtil
selectorObject.AddComponent(parSelectorComponent);
var innerSelectorObject = new GameObject
{
Transform = { Translation = new Vector3(-0.5f, 0, 0), Size = new Vector3(0.25f, 0.5f, 1f) }
Transform = { Translation = new Vector3(-0.5f, 0, -1), Size = new Vector3(0.25f, 0.5f, 1f) }
};
innerSelectorObject.AddComponent(new Box2DRenderer
{

View File

@@ -7,12 +7,12 @@ uniform mat4 uViewMatrix;
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec2 aUV;
layout (location = 2) in vec4 aColor;
layout (location = 3) in int aTextureId;
layout (location = 3) in float aTextureId;
layout (location = 4) in mat4 aModel;
layout (location = 0) out vec4 oColor;
layout (location = 1) out vec2 oUV;
layout (location = 2) flat out int oTextureId;
layout (location = 2) flat out float oTextureId;
void main()
{
@@ -30,7 +30,7 @@ uniform sampler2D uTexture[16];
layout (location = 0) in vec4 iColor;
layout (location = 1) in vec2 iUV;
layout (location = 2) flat in int iTextureId;
layout (location = 2) flat in float iTextureId;
layout (location = 0) out vec4 FragColor;
@@ -39,7 +39,7 @@ void main()
FragColor = iColor;
if (iTextureId >= 0)
FragColor *= texture(uTexture[iTextureId], iUV);
FragColor *= texture(uTexture[int(iTextureId)], iUV);
if (FragColor.a == 0.0)
discard;

View File

@@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
using OpenTK.Mathematics;
namespace Engine.Asset.Font.Metadata;

View File

@@ -5,11 +5,11 @@ namespace Engine.Asset.Mesh.Loader;
public class ObjMeshLoader : IMeshLoader
{
private static readonly ObjMeshLoader _instance = new();
private static readonly ObjMeshLoader INSTANCE = new();
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{
return _instance.LoadMesh(parReader, parParameters);
return INSTANCE.LoadMesh(parReader, parParameters);
}
private ObjMeshLoader()
@@ -62,7 +62,7 @@ public class ObjMeshLoader : IMeshLoader
break;
case "f":
for (var i = 1; i <= 3; i++)
for (var i = 1; i < parts.Length; i++)
{
var faceComponents = parts[i].Split('/');
var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] };

View File

@@ -5,11 +5,11 @@ namespace Engine.Asset.Mesh.Loader;
public class StlMeshLoader : IMeshLoader
{
private static readonly StlMeshLoader _instance = new();
private static readonly StlMeshLoader INSTANCE = new();
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{
return _instance.LoadMesh(parReader, parParameters);
return INSTANCE.LoadMesh(parReader, parParameters);
}
private StlMeshLoader()

View File

@@ -7,7 +7,7 @@ namespace Engine.Graphics.Render.Quad;
public struct QuadInstanceVertex : IVertex
{
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
[Vertex(VertexAttribType.Int)] public int _textureId;
[Vertex(VertexAttribType.Float)] public float _textureId;
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
public override int GetHashCode()

View File

@@ -40,7 +40,7 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
_instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;
_instanceVertices[_queuedInstanceCount]._color = parColor;
_instanceVertices[_queuedInstanceCount]._textureId = textureId;
_instanceVertices[_queuedInstanceCount]._textureId = textureId + 0.01f;
_frameHash = HashCode.Combine(_frameHash, _instanceVertices[_queuedInstanceCount]);

View File

@@ -1,11 +1,9 @@
using System.Collections.Generic;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Pixel;
using Engine.Graphics.Render.Quad;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using Serilog;
namespace Engine.Graphics;

View File

@@ -1,6 +1,4 @@
using System.Collections;
namespace Engine.Graphics.Texture;
namespace Engine.Graphics.Texture;
public class TextureUnitMap(int parCapacity)
{

View File

@@ -1,7 +1,5 @@
using System.Runtime.InteropServices;
using Engine.Graphics.Pixel;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

View File

@@ -7,10 +7,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace Engine {
using System;
namespace Engine.Resource {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>

View File

@@ -1,7 +1,6 @@
using Engine.Graphics.Camera;
using Engine.Graphics.Pipeline;
using OpenTK.Mathematics;
using Serilog;
namespace Engine.Scene.Component.BuiltIn;

View File

@@ -12,6 +12,7 @@ public class PerspectiveCamera(
{
public float FieldOfView { get; set; } = parFieldOfView;
public Vector3 Forward => new Vector4(0, 1, 0, 1).MulProject(GameObject.Transform.TransformMatrix).Xyz;
public override Matrix4 View
{
get

View File

@@ -31,6 +31,11 @@ public class Transform : Component
}
}
public Vector3 GetFullTranslation()
{
return FullTransformMatrix.ExtractTranslation();
}
public Transform Clone()
{
var clone =
@@ -38,4 +43,15 @@ public class Transform : Component
return clone;
}
public float SquaredDistanceTo(Transform parTransform)
{
var translation = GetFullTranslation();
var otherTranslation = parTransform.GetFullTranslation();
var difference = translation - otherTranslation;
var squaredDistance = difference.LengthSquared;
return squaredDistance;
}
}

View File

@@ -14,6 +14,10 @@ public abstract class Component : IUpdate, IRender
{
}
public virtual void PreUpdate()
{
}
public virtual void Update(double parDeltaTime)
{
}

View File

@@ -11,22 +11,26 @@ public sealed class GameObject : IUpdate, IRender
public bool IsEnabled
{
get => IsSelfEnabled && IsParentEnabled;
set => IsSelfEnabled = value;
set => _nextIsSelfEnabled = value;
}
private bool IsSelfEnabled { get; set; } = true;
private bool _prevIsSelfEnabled = true;
private bool _nextIsSelfEnabled = true;
private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true;
public Transform Transform { get; }
internal Scene? Scene { get; set; }
public Scene? Scene { get; set; }
private readonly Queue<Action> _componentActions = new();
private readonly List<Component.Component> _components = [];
private readonly HashSet<Type> _addedComponentTypes = [];
private readonly HashSet<Component.Component> _addedComponents = [];
private readonly HashSet<Component.Component> _removedComponents = [];
public GameObject()
{
AddComponent<Transform>();
@@ -43,20 +47,37 @@ public sealed class GameObject : IUpdate, IRender
Transform = GetComponent<Transform>()!;
}
public void Awake()
public void PreUpdate()
{
ProcessAddedComponents();
ProcessRemovedComponents();
foreach (var component in _components)
{
component.Awake();
component.PreUpdate();
}
}
public void Start()
private void ProcessAddedComponents()
{
foreach (var component in _components)
foreach (var component in _addedComponents)
{
component.Awake();
component.Start();
}
_addedComponents.Clear();
}
private void ProcessRemovedComponents()
{
foreach (var component in _removedComponents)
{
component.Destroy();
component.GameObject = null!;
}
_removedComponents.Clear();
}
public void Update(double parDeltaTime)
@@ -112,7 +133,44 @@ public sealed class GameObject : IUpdate, IRender
public T? GetComponent<T>() where T : Component.Component
{
return !HasComponent<T>() ? null : _components.OfType<T>().First();
if (!HasComponent<T>())
return null;
foreach (var component in _components)
{
if (component is T result)
return result;
}
return null;
}
public T? GetComponentAny<T>() where T : Component.Component
{
var component = GetComponent<T>();
if (component != null)
return component;
component = GetComponentInChildren<T>();
return component;
}
public T? GetComponentInChildren<T>() where T : Component.Component
{
var children = Scene!.Hierarchy.GetChildren(this);
foreach (var child in children)
{
var component = child.GetComponent<T>();
if (component != null)
return component;
var childComponent = child.GetComponentInChildren<T>();
if (childComponent != null)
return childComponent;
}
return null;
}
public void AddComponent<T>() where T : Component.Component, new()
@@ -142,6 +200,8 @@ public sealed class GameObject : IUpdate, IRender
public void AddComponent<T>(T parComponent) where T : Component.Component
{
parComponent.GameObject = this;
_componentActions.Enqueue(() =>
{
if (HasComponent<T>())
@@ -149,9 +209,9 @@ public sealed class GameObject : IUpdate, IRender
throw new ArgumentException($"GameObject already has component of type {typeof(T)}");
}
parComponent.GameObject = this;
_components.Add(parComponent);
_addedComponentTypes.Add(parComponent.GetType().GetComponentBaseType());
_addedComponents.Add(parComponent);
});
}
@@ -177,6 +237,7 @@ public sealed class GameObject : IUpdate, IRender
_components.Remove(component);
_addedComponentTypes.Remove(typeof(T));
_removedComponents.Add(component);
});
}
@@ -188,9 +249,26 @@ public sealed class GameObject : IUpdate, IRender
internal void ProcessChanges()
{
IsSelfEnabled = _nextIsSelfEnabled;
while (_componentActions.TryDequeue(out var action))
{
action();
}
}
public override string ToString()
{
return Id.ToString();
}
public override bool Equals(object? parObj)
{
return parObj is GameObject gameObject && Id == gameObject.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}

View File

@@ -50,6 +50,9 @@ public class Hierarchy<T>
_hierarchyActions.Enqueue(() =>
{
if (!Contains(parObj))
return;
var parent = GetParent(parObj);
_childrenLookup[parent].Remove(parObj);
@@ -97,9 +100,24 @@ public class Hierarchy<T>
: throw new InvalidOperationException($"Child {parChild} is not in hierarchy");
}
public IEnumerable<T> GetChildren(T? parObj = null)
public IEnumerable<T> GetChildren(T? parParent = null)
{
return _childrenLookup.TryGetValue(parObj, out IList<T>? children) ? children : Enumerable.Empty<T>();
return _childrenLookup.TryGetValue(parParent, out var children) ? children : Enumerable.Empty<T>();
}
public IEnumerable<T> GetAllChildren(T? parParent = null)
{
var children = GetChildren(parParent);
foreach (var child in children)
{
foreach (var descendant in GetAllChildren(child))
{
yield return descendant;
}
yield return child;
}
}
public bool IsInHierarchy(T? parAncestor, T? parChild)
@@ -133,19 +151,4 @@ public class Hierarchy<T>
return IsInHierarchy(parAncestor, parent);
}
public IEnumerable<T> GetAllChildren(T? parObj = null)
{
IEnumerable<T>? children = GetChildren(parObj);
foreach (var child in children)
{
yield return child;
foreach (var descendant in GetAllChildren(child))
{
yield return descendant;
}
}
}
}

View File

@@ -8,6 +8,8 @@ public class Scene : IUpdate, IRender
{
public bool IsPlaying { get; private set; }
public IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras;
public float TimeScale { get; set; } = 1.0f;
private readonly Dictionary<RenderLayer, ICamera> _cameras = new();
internal Hierarchy<GameObject> Hierarchy { get; } = new();
@@ -32,9 +34,11 @@ public class Scene : IUpdate, IRender
IsPlaying = true;
}
public List<T> FindAllComponents<T>() where T : Component.Component
public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component
{
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>())
return Hierarchy.Objects
.Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled)
.Select(parGameObject => parGameObject.GetComponent<T>())
.Where(parComponent => parComponent != null).ToList()!;
}
@@ -53,9 +57,16 @@ public class Scene : IUpdate, IRender
ProcessChanges();
foreach (var gameObject in Hierarchy.Objects)
var hierarchyObjects = Hierarchy.Objects;
foreach (var gameObject in hierarchyObjects)
{
gameObject.Update(parDeltaTime);
gameObject.PreUpdate();
}
foreach (var gameObject in hierarchyObjects)
{
gameObject.Update(parDeltaTime * TimeScale);
}
}
@@ -88,16 +99,9 @@ public class Scene : IUpdate, IRender
}
public void Add(GameObject parGameObject)
{
Hierarchy.Add(parGameObject);
_sceneActions.Enqueue(() =>
{
parGameObject.Scene = this;
parGameObject.Awake();
parGameObject.Start();
});
Hierarchy.Add(parGameObject);
}
public void AddChild(GameObject parParent, GameObject parChild)
@@ -108,11 +112,12 @@ public class Scene : IUpdate, IRender
public void Remove(GameObject parGameObject)
{
var children = Hierarchy.GetAllChildren(parGameObject).ToList();
Hierarchy.Remove(parGameObject);
_sceneActions.Enqueue(() =>
{
foreach (var child in Hierarchy.GetAllChildren(parGameObject))
foreach (var child in children)
{
child.Destroy();
child.Scene = null;
@@ -124,6 +129,11 @@ public class Scene : IUpdate, IRender
});
}
public IEnumerable<GameObject> GetChildren(GameObject parParent, bool parRecursive = false)
{
return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent);
}
private void ProcessChanges()
{
Hierarchy.ProcessChanges();

View File

@@ -5,7 +5,6 @@ public class SceneManager : IUpdate, IRender
public Scene? CurrentScene => _currentScene;
private Scene? _currentScene;
// private Scene? _nextScene;
private Func<Scene>? _nextScene;
public void TransitionTo(Func<Scene>? parScene)

View File

@@ -1,6 +1,6 @@
namespace Engine.Util;
public class Timer
public class TickableTimer
{
public event Action? OnFinished;
public event Action<double>? OnUpdate;
@@ -41,7 +41,7 @@ public class Timer
private double _totalTime;
private double _currentTime;
public Timer(double parTotalTime)
public TickableTimer(double parTotalTime)
{
if (parTotalTime <= 0)
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parTotalTime);

View File

@@ -1,7 +1,6 @@
using Engine.Graphics;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;

View File

@@ -8,7 +8,7 @@ using Engine.Resource;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using PresenterConsole.Resource;
using ShaderResource = PresenterConsole.Resource.ShaderResource;
namespace PresenterConsole;

View File

@@ -1,6 +1,4 @@
using System.Configuration;
using System.Data;
using System.IO;
using System.IO;
using System.Windows;
using Engine;
using Engine.Graphics;