diff --git a/.gitignore b/.gitignore
index 1788a79..896640a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/DungeonShooting_Godot/android
+/DungeonShooting_Godot/.idea
diff --git a/DungeonShooting_Godot/DungeonShooting.sln.DotSettings.user b/DungeonShooting_Godot/DungeonShooting.sln.DotSettings.user
new file mode 100644
index 0000000..5ef2d60
--- /dev/null
+++ b/DungeonShooting_Godot/DungeonShooting.sln.DotSettings.user
@@ -0,0 +1,2 @@
+
+ WARNING
\ No newline at end of file
diff --git a/DungeonShooting_Godot/prefab/effect/Hit.tscn b/DungeonShooting_Godot/prefab/effect/Hit.tscn
index 685cc64..bbbbd32 100644
--- a/DungeonShooting_Godot/prefab/effect/Hit.tscn
+++ b/DungeonShooting_Godot/prefab/effect/Hit.tscn
@@ -5,7 +5,8 @@
[ext_resource path="res://resource/sprite/effect/hit/hit4.png" type="Texture" id=3]
[ext_resource path="res://resource/sprite/effect/hit/hit0.png" type="Texture" id=4]
[ext_resource path="res://resource/sprite/effect/hit/hit3.png" type="Texture" id=5]
-[ext_resource path="res://src/effect/Hit.cs" type="Script" id=6]
+[ext_resource path="res://src/game/effect/Hit.cs" type="Script" id=6]
+
[sub_resource type="SpriteFrames" id=1]
animations = [ {
diff --git a/DungeonShooting_Godot/prefab/role/Player.tscn b/DungeonShooting_Godot/prefab/role/Player.tscn
index f89032d..927bc6b 100644
--- a/DungeonShooting_Godot/prefab/role/Player.tscn
+++ b/DungeonShooting_Godot/prefab/role/Player.tscn
@@ -1,9 +1,10 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://prefab/role/Role.tscn" type="PackedScene" id=1]
-[ext_resource path="res://src/role/Player.cs" type="Script" id=2]
+[ext_resource path="res://src/game/role/Player.cs" type="Script" id=2]
[ext_resource path="res://prefab/weapon/Weapon.tscn" type="PackedScene" id=4]
+
[node name="Player" instance=ExtResource( 1 )]
collision_layer = 8
script = ExtResource( 2 )
diff --git a/DungeonShooting_Godot/prefab/role/Role.tscn b/DungeonShooting_Godot/prefab/role/Role.tscn
index a3c93fe..76a842c 100644
--- a/DungeonShooting_Godot/prefab/role/Role.tscn
+++ b/DungeonShooting_Godot/prefab/role/Role.tscn
@@ -1,9 +1,10 @@
[gd_scene load_steps=19 format=2]
-[ext_resource path="res://src/role/Role.cs" type="Script" id=1]
+[ext_resource path="res://src/game/role/Role.cs" type="Script" id=1]
[ext_resource path="res://resource/sprite/role/role2.png" type="Texture" id=2]
[ext_resource path="res://resource/sprite/role/role1.png" type="Texture" id=3]
+
[sub_resource type="AtlasTexture" id=17]
atlas = ExtResource( 3 )
region = Rect2( 0, 24, 16, 24 )
diff --git a/DungeonShooting_Godot/prefab/ui/Cursor.tscn b/DungeonShooting_Godot/prefab/ui/Cursor.tscn
index 4eae533..c2d7b54 100644
--- a/DungeonShooting_Godot/prefab/ui/Cursor.tscn
+++ b/DungeonShooting_Godot/prefab/ui/Cursor.tscn
@@ -1,7 +1,8 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://resource/sprite/ui/Cursor.png" type="Texture" id=1]
-[ext_resource path="res://src/effect/Cursor.cs" type="Script" id=2]
+[ext_resource path="res://src/game/effect/Cursor.cs" type="Script" id=2]
+
[node name="Cursor" type="Node2D"]
z_index = 10
diff --git a/DungeonShooting_Godot/prefab/ui/RoomUI.tscn b/DungeonShooting_Godot/prefab/ui/RoomUI.tscn
index ec1aa61..9091777 100644
--- a/DungeonShooting_Godot/prefab/ui/RoomUI.tscn
+++ b/DungeonShooting_Godot/prefab/ui/RoomUI.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=11 format=2]
-[ext_resource path="res://src/ui/RoomUI.cs" type="Script" id=1]
+[ext_resource path="res://src/game/ui/RoomUI.cs" type="Script" id=1]
[ext_resource path="res://resource/sprite/ui/mapBar.png" type="Texture" id=2]
[ext_resource path="res://prefab/ui/bar/ReloadBar.tscn" type="PackedScene" id=3]
[ext_resource path="res://resource/sprite/ui/healthBar.png" type="Texture" id=4]
diff --git a/DungeonShooting_Godot/prefab/ui/bar/InteractiveTipBar.tscn b/DungeonShooting_Godot/prefab/ui/bar/InteractiveTipBar.tscn
index 23ee05f..98512bd 100644
--- a/DungeonShooting_Godot/prefab/ui/bar/InteractiveTipBar.tscn
+++ b/DungeonShooting_Godot/prefab/ui/bar/InteractiveTipBar.tscn
@@ -1,11 +1,12 @@
[gd_scene load_steps=7 format=2]
-[ext_resource path="res://src/ui/InteractiveTipBar.cs" type="Script" id=1]
+[ext_resource path="res://src/game/ui/InteractiveTipBar.cs" type="Script" id=1]
[ext_resource path="res://resource/sprite/ui/keyboard/e.png" type="Texture" id=2]
[ext_resource path="res://resource/sprite/ui/icon/icon_bullet.png" type="Texture" id=3]
[ext_resource path="res://resource/sprite/ui/font_bg.png" type="Texture" id=4]
[ext_resource path="res://resource/font/cn_font_4.tres" type="DynamicFont" id=5]
+
[sub_resource type="Gradient" id=1]
colors = PoolColorArray( 0.4, 0.498039, 1, 1, 0.4, 0.498039, 1, 0.313726 )
diff --git a/DungeonShooting_Godot/prefab/ui/bar/ReloadBar.tscn b/DungeonShooting_Godot/prefab/ui/bar/ReloadBar.tscn
index b9af2ad..41c5c96 100644
--- a/DungeonShooting_Godot/prefab/ui/bar/ReloadBar.tscn
+++ b/DungeonShooting_Godot/prefab/ui/bar/ReloadBar.tscn
@@ -1,9 +1,10 @@
[gd_scene load_steps=4 format=2]
-[ext_resource path="res://src/ui/ReloadBar.cs" type="Script" id=1]
+[ext_resource path="res://src/game/ui/ReloadBar.cs" type="Script" id=1]
[ext_resource path="res://resource/sprite/ui/reloadBarBlock.png" type="Texture" id=2]
[ext_resource path="res://resource/sprite/ui/reloadBar.png" type="Texture" id=3]
+
[node name="ReloadBar" type="Node2D"]
script = ExtResource( 1 )
diff --git a/DungeonShooting_Godot/prefab/weapon/bullet/HighSpeedBullet.tscn b/DungeonShooting_Godot/prefab/weapon/bullet/HighSpeedBullet.tscn
index f3a97b6..e309e2f 100644
--- a/DungeonShooting_Godot/prefab/weapon/bullet/HighSpeedBullet.tscn
+++ b/DungeonShooting_Godot/prefab/weapon/bullet/HighSpeedBullet.tscn
@@ -1,8 +1,9 @@
[gd_scene load_steps=5 format=2]
-[ext_resource path="res://src/weapon/bullet/HighSpeedBullet.cs" type="Script" id=1]
+[ext_resource path="res://src/game/props/weapon/bullet/HighSpeedBullet.cs" type="Script" id=1]
[ext_resource path="res://prefab/effect/Hit.tscn" type="PackedScene" id=2]
+
[sub_resource type="Curve" id=1]
_data = [ Vector2( 0, 0.781588 ), 0.0, 0.0, 0, 0, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ]
diff --git a/DungeonShooting_Godot/prefab/weapon/bullet/OrdinaryBullets.tscn b/DungeonShooting_Godot/prefab/weapon/bullet/OrdinaryBullets.tscn
index f9d4848..6297774 100644
--- a/DungeonShooting_Godot/prefab/weapon/bullet/OrdinaryBullets.tscn
+++ b/DungeonShooting_Godot/prefab/weapon/bullet/OrdinaryBullets.tscn
@@ -1,9 +1,10 @@
[gd_scene load_steps=4 format=2]
-[ext_resource path="res://src/weapon/bullet/OrdinaryBullets.cs" type="Script" id=1]
+[ext_resource path="res://src/game/props/weapon/bullet/OrdinaryBullets.cs" type="Script" id=1]
[ext_resource path="res://resource/sprite/bullet/bullet.png" type="Texture" id=2]
[ext_resource path="res://prefab/effect/Hit.tscn" type="PackedScene" id=3]
+
[node name="OrdinaryBullets" type="Node2D"]
script = ExtResource( 1 )
Hit = ExtResource( 3 )
diff --git a/DungeonShooting_Godot/project.godot b/DungeonShooting_Godot/project.godot
index 8ed3c3a..4470bd7 100644
--- a/DungeonShooting_Godot/project.godot
+++ b/DungeonShooting_Godot/project.godot
@@ -16,7 +16,7 @@
[autoload]
-GameManager="*res://src/manager/GameManager.cs"
+GameManager="*res://src/game/manager/GameManager.cs"
[display]
diff --git a/DungeonShooting_Godot/scene/Room.tscn b/DungeonShooting_Godot/scene/Room.tscn
index 870408c..dc60f87 100644
--- a/DungeonShooting_Godot/scene/Room.tscn
+++ b/DungeonShooting_Godot/scene/Room.tscn
@@ -2,9 +2,9 @@
[ext_resource path="res://prefab/role/Player.tscn" type="PackedScene" id=1]
[ext_resource path="res://resource/map/dungeon_test.tmx" type="PackedScene" id=2]
-[ext_resource path="res://src/room/RoomManager.cs" type="Script" id=3]
+[ext_resource path="res://src/game/room/RoomManager.cs" type="Script" id=3]
[ext_resource path="res://prefab/ui/Cursor.tscn" type="PackedScene" id=4]
-[ext_resource path="res://src/camera/MainCamera.cs" type="Script" id=5]
+[ext_resource path="res://src/game/camera/MainCamera.cs" type="Script" id=5]
[ext_resource path="res://prefab/ui/RoomUI.tscn" type="PackedScene" id=6]
[node name="Room" type="Node2D"]
diff --git a/DungeonShooting_Godot/scene/TestNavigation.tscn b/DungeonShooting_Godot/scene/TestNavigation.tscn
index b2f8768..7db499e 100644
--- a/DungeonShooting_Godot/scene/TestNavigation.tscn
+++ b/DungeonShooting_Godot/scene/TestNavigation.tscn
@@ -1,9 +1,10 @@
[gd_scene load_steps=8 format=2]
-[ext_resource path="res://src/TestNavigation.cs" type="Script" id=1]
+[ext_resource path="res://src/game/TestNavigation.cs" type="Script" id=1]
[ext_resource path="res://icon.png" type="Texture" id=2]
[ext_resource path="res://resource/sprite/environment/craftpix-net-248911/16x16.png" type="Texture" id=3]
+
[sub_resource type="NavigationPolygon" id=2]
vertices = PoolVector2Array( 0, 0, 16, 0, 16, 16, 0, 16 )
polygons = [ PoolIntArray( 0, 1, 2, 3 ) ]
diff --git a/DungeonShooting_Godot/src/AnimatorNames.cs b/DungeonShooting_Godot/src/AnimatorNames.cs
deleted file mode 100644
index 5645333..0000000
--- a/DungeonShooting_Godot/src/AnimatorNames.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-
-public static class AnimatorNames
-{
- public static readonly string Idle = "idle";
- public static readonly string Run = "run";
- public static readonly string ReverseRun = "reverseRun";
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/CollisionComponent.cs b/DungeonShooting_Godot/src/CollisionComponent.cs
deleted file mode 100644
index 14140e5..0000000
--- a/DungeonShooting_Godot/src/CollisionComponent.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Godot;
-using System;
-
-public class CollisionComponent
-{
- public IObject Target { get; }
-
- public Area2D AreaNode { get; }
-
- public bool Disabled
- {
- get
- {
- return CollisionShape2DNode.Disabled;
- }
- set
- {
- CollisionShape2DNode.Disabled = value;
- }
- }
-
- public CollisionShape2D CollisionShape2DNode { get; }
-
- public CollisionComponent(IObject inst, Area2D area, CollisionShape2D collisionShape)
- {
- Target = inst;
- AreaNode = area;
- CollisionShape2DNode = collisionShape;
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/GameConfig.cs b/DungeonShooting_Godot/src/GameConfig.cs
deleted file mode 100644
index 91ebe06..0000000
--- a/DungeonShooting_Godot/src/GameConfig.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-
-public static class GameConfig
-{
- ///
- /// 散射计算的默认距离
- ///
- public static readonly float ScatteringDistance = 300;
- ///
- /// 重力加速度
- ///
- public static readonly float G = 250f;
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/IObject.cs b/DungeonShooting_Godot/src/IObject.cs
deleted file mode 100644
index 4afba80..0000000
--- a/DungeonShooting_Godot/src/IObject.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Godot;
-using System;
-
-///
-/// 房间内所有物体都必须实现该接口
-///
-public interface IObject
-{
- float GlobalHeight { get; set; }
-
- Vector2 GlobalPosition { get; set; }
-
- CollisionComponent Collision { get; }
-
- void OnOtherEnter(IObject other);
-
- void OnOtherExit(IObject other);
-
- void Destroy();
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/Pack.cs b/DungeonShooting_Godot/src/Pack.cs
deleted file mode 100644
index a85287a..0000000
--- a/DungeonShooting_Godot/src/Pack.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-
-// public class Pack
-// {
-// public uint Size { get; private set; }
-
-// public Pack(uint size)
-// {
-// Size = size;
-// }
-
-
-// }
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/TestNavigation.cs b/DungeonShooting_Godot/src/TestNavigation.cs
deleted file mode 100644
index 16ededf..0000000
--- a/DungeonShooting_Godot/src/TestNavigation.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Godot;
-
-public class TestNavigation : Node2D
-{
-
- private Navigation2D Navigation2D;
- private Vector2[] points = new Vector2[0];
-
- public override void _Ready()
- {
- Navigation2D = GetNode("Position2D/Navigation2D");
- }
-
- public override void _Input(InputEvent @event)
- {
- if (@event is InputEventMouseButton ieb) {
- if (ieb.ButtonIndex == (int)ButtonList.Left && ieb.Pressed)
- {
- points = Navigation2D.GetSimplePath(Vector2.Zero, Navigation2D.ToLocal(ieb.Position));
- Update();
- string str = "";
- foreach (var item in points)
- {
- str += item;
- }
- GD.Print("路径: " + points.Length + ", " + str);
- }
- }
- }
- public override void _Draw()
- {
- if (points.Length >= 2) {
- GD.Print("绘制线段...");
- DrawPolyline(points, Colors.Red);
- // DrawMultiline(points, Colors.Red);
- }
- }
-
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/camera/MainCamera.cs b/DungeonShooting_Godot/src/camera/MainCamera.cs
deleted file mode 100644
index 8f97587..0000000
--- a/DungeonShooting_Godot/src/camera/MainCamera.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using System.Collections.Generic;
-using Godot;
-
-public class MainCamera : Camera2D
-{
- ///
- /// 当前场景的相机对象
- ///
- public static MainCamera Main { get; private set; }
-
- ///
- /// 恢复系数
- ///
- [Export]
- public float RecoveryCoefficient = 100f;
- ///
- /// 抖动开关
- ///
- public bool Enable { get; set; } = true;
-
- private long _index = 0;
- private Vector2 _prossesDistance = Vector2.Zero;
- private Vector2 _prossesDirectiona = Vector2.Zero;
- private readonly Dictionary _shakeMap = new Dictionary();
-
- public override void _Ready()
- {
- Main = this;
- }
- public override void _PhysicsProcess(float delta)
- {
- _Shake(delta);
- }
-
- ///
- /// 设置帧抖动, 结束后自动清零, 需要每一帧调用
- ///
- /// 抖动的力度
- public void ProssesShake(Vector2 value)
- {
- if (value.Length() > _prossesDistance.Length())
- {
- _prossesDistance = value;
- }
- }
-
- public void ProssesDirectionalShake(Vector2 value)
- {
- _prossesDirectiona += value;
- }
-
- ///
- /// 创建一个抖动, 并设置抖动时间
- ///
- /// 抖动力度
- /// 抖动生效时间
- public async void CreateShake(Vector2 value, float time)
- {
- if (time > 0)
- {
- long tempIndex = _index++;
- SceneTreeTimer sceneTreeTimer = GetTree().CreateTimer(time);
- _shakeMap[tempIndex] = value;
- await ToSignal(sceneTreeTimer, "timeout");
- _shakeMap.Remove(tempIndex);
- }
- }
-
- //抖动调用
- private void _Shake(float delta)
- {
- if (Enable)
- {
- var _distance = _CalculateDistance();
- Offset += new Vector2(
- (float)GD.RandRange(-_distance.x, _distance.x) - Offset.x,
- (float)GD.RandRange(-_distance.y, _distance.y) - Offset.y
- );
- Offset += _prossesDirectiona;
- _prossesDistance = Vector2.Zero;
- _prossesDirectiona = Vector2.Zero;
- }
- else
- {
- Offset = Offset.LinearInterpolate(Vector2.Zero, RecoveryCoefficient * delta);
- }
- }
-
- //计算相机需要抖动的值
- private Vector2 _CalculateDistance()
- {
- Vector2 temp = Vector2.Zero;
- float length = 0;
- foreach (var item in _shakeMap)
- {
- var value = item.Value;
- float tempLenght = value.Length();
- if (tempLenght > length)
- {
- length = tempLenght;
- temp = value;
- }
- }
- return _prossesDistance.Length() > length ? _prossesDistance : temp;
- }
-
-}
diff --git a/DungeonShooting_Godot/src/common/NodeExtend.cs b/DungeonShooting_Godot/src/common/NodeExtend.cs
deleted file mode 100644
index c1083a9..0000000
--- a/DungeonShooting_Godot/src/common/NodeExtend.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using Godot;
-using System;
-
-///
-/// 该类为 node 节点通用扩展函数类
-///
-public static class NodeExtend
-{
-
- public static ThrowNode StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate)
- {
- return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate);
- }
-
- public static T StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate) where T : ThrowNode
- {
- return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate, null);
- }
-
- public static ThrowNode StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Sprite shadowTarget)
- {
- return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate, shadowTarget);
- }
-
- public static T StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Sprite shadowTarget) where T : ThrowNode
- {
- ThrowNode throwNode = node.GetParentOrNull();
- T inst;
- if (throwNode == null)
- {
- inst = Activator.CreateInstance();
- }
- else if (throwNode is T)
- {
- inst = throwNode as T;
- }
- else
- {
- throwNode.StopThrow();
- inst = Activator.CreateInstance();
- }
- inst.StartThrow(size, start, startHeight, direction, xSpeed, ySpeed, rotate, node, shadowTarget);
- return inst;
- }
-
- ///
- /// 将一个节点扔到地上, 并设置显示的阴影, 函数返回根据该节点创建的 ThrowNode 节点
- ///
- /// 显示的阴影sprite
- public static ThrowNode PutDown(this Node2D node, Sprite shadowTarget)
- {
- return StartThrow(node, Vector2.Zero, node.Position, 0, 0, 0, 0, 0, shadowTarget);
- }
-
- ///
- /// 拾起一个 node 节点, 返回是否拾起成功
- ///
- public static bool Pickup(this Node2D node)
- {
- ThrowNode parent = node.GetParentOrNull();
- if (parent != null)
- {
- parent.StopThrow();
- return true;
- }
- return false;
- }
-
- ///
- /// 触发扔掉武器操作
- ///
- /// 触发扔掉该武器的的角色
- public static ThrowWeapon StartThrowWeapon(this Weapon weapon, Role master)
- {
- if (master.Face == FaceDirection.Left)
- {
- weapon.Scale *= new Vector2(1, -1);
- weapon.RotationDegrees = 180;
- }
- var startPos = master.GlobalPosition;// + new Vector2(0, 0);
- var startHeight = 6;
- var direction = master.GlobalRotationDegrees + MathUtils.RandRangeInt(-20, 20);
- var xf = 30;
- var yf = MathUtils.RandRangeInt(60, 120);
- var rotate = MathUtils.RandRangeInt(-180, 180);
- weapon.Position = Vector2.Zero;
- return weapon.StartThrow(new Vector2(20, 20), startPos, startHeight, direction, xf, yf, rotate, weapon.WeaponSprite);
- }
-
-
- ///
- /// 尝试将一个node2d节点转换成一个 IProp 类
- ///
- public static IProp AsProp(this Node2D node2d)
- {
- if (node2d is IProp p)
- {
- return p;
- }
- var parent = node2d.GetParent();
- if (parent != null && parent is IProp p2)
- {
- return p2;
- }
- return null;
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/effect/Cursor.cs b/DungeonShooting_Godot/src/effect/Cursor.cs
deleted file mode 100644
index a2d6dd4..0000000
--- a/DungeonShooting_Godot/src/effect/Cursor.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using Godot;
-
-///
-/// 鼠标指针
-///
-public class Cursor : Node2D
-{
-
- private Sprite lt;
- private Sprite lb;
- private Sprite rt;
- private Sprite rb;
-
- public override void _Ready()
- {
- lt = GetNode("LT");
- lb = GetNode("LB");
- rt = GetNode("RT");
- rb = GetNode("RB");
- }
-
- public override void _Process(float delta)
- {
- var targetGun = RoomManager.Current?.Player?.Holster.ActiveWeapon;
- if (targetGun != null)
- {
- SetScope(targetGun.CurrScatteringRange, targetGun);
- }
- else
- {
- SetScope(0, targetGun);
- }
- SetCursorPos();
- }
-
- ///
- /// 设置光标半径范围
- ///
- private void SetScope(float scope, Weapon targetGun)
- {
- if (targetGun != null)
- {
- var len = GlobalPosition.DistanceTo(targetGun.GlobalPosition);
- if (targetGun.Attribute != null)
- {
- len = Mathf.Max(0, len - targetGun.Attribute.FirePosition.x);
- }
- scope = len / GameConfig.ScatteringDistance * scope;
- }
- lt.Position = new Vector2(-scope, -scope);
- lb.Position = new Vector2(-scope, scope);
- rt.Position = new Vector2(scope, -scope);
- rb.Position = new Vector2(scope, scope);
- }
-
- private void SetCursorPos()
- {
- Position = GetGlobalMousePosition();
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/effect/Hit.cs b/DungeonShooting_Godot/src/effect/Hit.cs
deleted file mode 100644
index 99b4b4d..0000000
--- a/DungeonShooting_Godot/src/effect/Hit.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Godot;
-
-public class Hit : AnimatedSprite
-{
-
- public override void _Ready()
- {
- Frame = 0;
- Playing = true;
- }
-
- ///
- /// 动画结束, 销毁
- ///
- private void _on_Hit_animation_finished()
- {
- QueueFree();
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/effect/ThrowNode.cs b/DungeonShooting_Godot/src/effect/ThrowNode.cs
deleted file mode 100644
index 5073764..0000000
--- a/DungeonShooting_Godot/src/effect/ThrowNode.cs
+++ /dev/null
@@ -1,262 +0,0 @@
-using Godot;
-
-///
-/// 模拟抛出的物体, 使用时将对象挂载到该节点上即可
-///
-public class ThrowNode : KinematicBody2D
-{
- ///
- /// 是否已经结束
- ///
- public bool IsOver { get; protected set; } = true;
- ///
- /// 物体大小
- ///
- public Vector2 Size { get; protected set; }
- ///
- /// 起始坐标
- ///
- public Vector2 StartPosition { get; protected set; }
- ///
- /// 移动方向, 0 - 360
- ///
- public float Direction { get; protected set; }
- ///
- /// x速度, 也就是水平速度
- ///
- public float XSpeed { get; protected set; }
- ///
- /// y轴速度, 也就是竖直速度
- ///
- public float YSpeed { get; protected set; }
- ///
- /// 初始x轴组队
- ///
- public float StartXSpeed { get; protected set; }
- ///
- /// 初始y轴速度
- ///
- public float StartYSpeed { get; protected set; }
- ///
- /// 旋转速度
- ///
- public float RotateSpeed { get; protected set; }
- ///
- /// 挂载的对象
- ///
- public Node2D Mount { get; protected set; }
- ///
- /// 碰撞组件
- ///
- public CollisionShape2D CollisionShape { get; protected set; }
- ///
- /// 绘制阴影的精灵
- ///
- public Sprite ShadowSprite { get; protected set; }
-
- protected Sprite ShadowTarget { get; set; }
-
- private bool inversionX = false;
-
- public ThrowNode()
- {
- Name = "ThrowNode";
- }
-
- public override void _Ready()
- {
- //只与墙壁碰撞
- CollisionMask = 1;
- CollisionLayer = 0;
- //创建碰撞器
- if (Size != Vector2.Zero)
- {
- CollisionShape = new CollisionShape2D();
- CollisionShape.Name = "Collision";
- var shape = new RectangleShape2D();
- shape.Extents = Size * 0.5f;
- CollisionShape.Shape = shape;
- AddChild(CollisionShape);
- }
- }
-
- ///
- /// 初始化该抛物线对象的基础数据
- ///
- /// 抛射的物体所占大小, 用于碰撞检测
- /// 起始点
- /// 起始高度
- /// 角度, 0 - 360
- /// 横轴速度
- /// 纵轴速度
- /// 旋转速度
- /// 需要挂载的节点
- /// 抛射的节点显示的纹理, 用于渲染阴影用
- public virtual void StartThrow(Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Node2D mount)
- {
- if (CollisionShape != null)
- {
- CollisionShape.Disabled = false;
- }
-
- IsOver = false;
- Size = size;
- GlobalPosition = StartPosition = start;
- Direction = direction;
- XSpeed = xSpeed;
- YSpeed = ySpeed;
- StartXSpeed = xSpeed;
- StartYSpeed = ySpeed;
- RotateSpeed = rotate;
-
- if (mount != null)
- {
- Mount = mount;
- var mountParent = mount.GetParent();
- if (mountParent == null)
- {
- AddChild(mount);
- }
- else if (mountParent != this)
- {
- mountParent.RemoveChild(mount);
- AddChild(mount);
- }
- mount.Position = new Vector2(0, -startHeight);
- }
- var parent = GetParent();
- if (parent == null)
- {
- RoomManager.Current.SortRoot.AddChild(this);
- }
- else if (parent == RoomManager.Current.SortRoot)
- {
- parent.RemoveChild(this);
- RoomManager.Current.SortRoot.AddChild(this);
- }
- }
-
- ///
- /// 初始化该抛物线对象的基础数据, 并且渲染阴影
- ///
- /// 抛射的物体所占大小, 用于碰撞检测
- /// 起始点
- /// 起始高度
- /// 角度, 0 - 360
- /// 横轴速度
- /// 纵轴速度
- /// 旋转速度
- /// 需要挂载的节点
- /// 抛射的节点显示的纹理, 用于渲染阴影用
- public virtual void StartThrow(Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Node2D mount, Sprite shadowTarget)
- {
- StartThrow(size, start, startHeight, direction, xSpeed, ySpeed, rotate, mount);
- ShadowTarget = shadowTarget;
- if (shadowTarget != null)
- {
- if (ShadowSprite == null)
- {
- //阴影
- ShadowSprite = new Sprite();
- ShadowSprite.Name = "Shadow";
- ShadowSprite.ZIndex = -5;
- ShadowSprite.Material = ResourceManager.ShadowMaterial;
- AddChild(ShadowSprite);
- }
- inversionX = mount.Scale.y < 0 ? true : false;
- if (inversionX)
- {
- ShadowSprite.Scale = shadowTarget.Scale * new Vector2(1, -1);
- }
- else
- {
- ShadowSprite.Scale = shadowTarget.Scale;
- }
- ShadowSprite.Texture = shadowTarget.Texture;
- }
- else if (ShadowSprite != null)
- {
- ShadowSprite.Texture = null;
- }
- }
-
- ///
- /// 停止投抛运动
- ///
- public Node2D StopThrow()
- {
- var mount = Mount;
- var parent = GetParent();
- if (parent != null && mount != null)
- {
- var gp = mount.GlobalPosition;
- var gr = mount.GlobalRotation;
- IsOver = true;
- Mount = null;
- RemoveChild(mount);
- parent.AddChild(mount);
- mount.GlobalPosition = gp;
- mount.GlobalRotation = gr;
- }
- QueueFree();
- return mount;
- }
-
- ///
- /// 达到最高点时调用
- ///
- protected virtual void OnMaxHeight(float height)
- {
-
- }
-
- ///
- /// 结束的调用
- ///
- protected virtual void OnOver()
- {
- GetParent().RemoveChild(this);
- RoomManager.Current.ObjectRoot.AddChild(this);
- if (CollisionShape != null)
- {
- CollisionShape.Disabled = true;
- }
- }
-
- public override void _Process(float delta)
- {
- if (!IsOver)
- {
- if (Mount == null)
- {
- QueueFree();
- return;
- }
- MoveAndSlide(new Vector2(XSpeed, 0).Rotated(Direction * Mathf.Pi / 180));
- Mount.Position = new Vector2(0, Mount.Position.y - YSpeed * delta);
- var rotate = Mount.GlobalRotationDegrees + RotateSpeed * delta;
- Mount.GlobalRotationDegrees = rotate;
- if (ShadowSprite != null)
- {
- ShadowSprite.GlobalRotationDegrees = rotate;
- // ShadowSprite.GlobalRotationDegrees = rotate + (inversionX ? 180 : 0);
- ShadowSprite.GlobalPosition = ShadowTarget.GlobalPosition + new Vector2(0, 1 - Mount.Position.y);
- }
- var ysp = YSpeed;
- YSpeed -= GameConfig.G * delta;
- //达到最高点
- if (ysp * YSpeed < 0)
- {
- OnMaxHeight(-Mount.Position.y);
- }
- //落地判断
- if (Mount.Position.y >= 0)
- {
- Mount.Position = new Vector2(0, 0);
- IsOver = true;
- OnOver();
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/framework/Component.cs b/DungeonShooting_Godot/src/framework/Component.cs
new file mode 100644
index 0000000..87cf541
--- /dev/null
+++ b/DungeonShooting_Godot/src/framework/Component.cs
@@ -0,0 +1,21 @@
+using Godot;
+
+public abstract class Component : IProcess where TN : Node where TG : Node2D
+{
+ public GameObject GameObject { get; private set; }
+ public TN Node { get; }
+
+ public Component(TN inst)
+ {
+ Node = inst;
+ }
+
+ public abstract void Process(float delta);
+
+ public abstract void PhysicsProcess(float delta);
+
+ public void SetGameObject(GameObject gameObject)
+ {
+ GameObject = gameObject;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/framework/GameObject.cs b/DungeonShooting_Godot/src/framework/GameObject.cs
new file mode 100644
index 0000000..421e504
--- /dev/null
+++ b/DungeonShooting_Godot/src/framework/GameObject.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using Godot;
+
+public class GameObject where T : Node2D
+{
+ public Vector3 Position { get; set; }
+ public Vector2 Position2D { get; set; }
+
+ public T Node;
+
+ private List _components = new List();
+
+ public GameObject(T node)
+ {
+ Node = node;
+ }
+
+ public void AddComponent(Component component) where TN : Node
+ {
+ if (!_components.Contains(component))
+ {
+ component.SetGameObject(this);
+ Node.AddChild(component.Node);
+ }
+ }
+
+ public void RemoveComponent(Component component) where TN : Node
+ {
+ if (_components.Remove(component))
+ {
+ component.SetGameObject(null);
+ Node.RemoveChild(component.Node);
+ }
+ }
+
+ public void Process(float delta)
+ {
+ var arr = _components.ToArray();
+ for (int i = 0; i < arr.Length; i++)
+ {
+ arr[i].Process(delta);
+ }
+ }
+
+ public void PhysicsProcess(float delta)
+ {
+ var arr = _components.ToArray();
+ for (int i = 0; i < arr.Length; i++)
+ {
+ arr[i].PhysicsProcess(delta);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/framework/IProcess.cs b/DungeonShooting_Godot/src/framework/IProcess.cs
new file mode 100644
index 0000000..3e5777f
--- /dev/null
+++ b/DungeonShooting_Godot/src/framework/IProcess.cs
@@ -0,0 +1,16 @@
+
+///
+/// 帧循环接口
+///
+public interface IProcess
+{
+ ///
+ /// 普通帧每帧调用
+ ///
+ void Process(float delta);
+
+ ///
+ /// 物理帧每帧调用
+ ///
+ void PhysicsProcess(float delta);
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/GameConfig.cs b/DungeonShooting_Godot/src/game/GameConfig.cs
new file mode 100644
index 0000000..91ebe06
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/GameConfig.cs
@@ -0,0 +1,12 @@
+
+public static class GameConfig
+{
+ ///
+ /// 散射计算的默认距离
+ ///
+ public static readonly float ScatteringDistance = 300;
+ ///
+ /// 重力加速度
+ ///
+ public static readonly float G = 250f;
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/TestNavigation.cs b/DungeonShooting_Godot/src/game/TestNavigation.cs
new file mode 100644
index 0000000..f393083
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/TestNavigation.cs
@@ -0,0 +1,40 @@
+using Godot;
+
+public class TestNavigation : Node2D
+{
+
+ private Navigation2D Navigation2D;
+ private Vector2[] points = new Vector2[0];
+
+ public override void _Ready()
+ {
+ Navigation2D = GetNode("Position2D/Navigation2D");
+ }
+
+ public override void _Input(InputEvent @event)
+ {
+ if (@event is InputEventMouseButton ieb) {
+ if (ieb.ButtonIndex == (int)ButtonList.Left && ieb.Pressed)
+ {
+ points = Navigation2D.GetSimplePath(Vector2.Zero, Navigation2D.ToLocal(ieb.Position));
+ Update();
+ string str = "";
+ foreach (var item in points)
+ {
+ str += item;
+ }
+ GD.Print("路径: " + points.Length + ", " + str);
+ }
+ }
+ }
+
+ public override void _Draw()
+ {
+ if (points.Length >= 2)
+ {
+ GD.Print("绘制线段...");
+ DrawPolyline(points, Colors.Red);
+ // DrawMultiline(points, Colors.Red);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/camera/MainCamera.cs b/DungeonShooting_Godot/src/game/camera/MainCamera.cs
new file mode 100644
index 0000000..8f97587
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/camera/MainCamera.cs
@@ -0,0 +1,107 @@
+using System.Collections.Generic;
+using Godot;
+
+public class MainCamera : Camera2D
+{
+ ///
+ /// 当前场景的相机对象
+ ///
+ public static MainCamera Main { get; private set; }
+
+ ///
+ /// 恢复系数
+ ///
+ [Export]
+ public float RecoveryCoefficient = 100f;
+ ///
+ /// 抖动开关
+ ///
+ public bool Enable { get; set; } = true;
+
+ private long _index = 0;
+ private Vector2 _prossesDistance = Vector2.Zero;
+ private Vector2 _prossesDirectiona = Vector2.Zero;
+ private readonly Dictionary _shakeMap = new Dictionary();
+
+ public override void _Ready()
+ {
+ Main = this;
+ }
+ public override void _PhysicsProcess(float delta)
+ {
+ _Shake(delta);
+ }
+
+ ///
+ /// 设置帧抖动, 结束后自动清零, 需要每一帧调用
+ ///
+ /// 抖动的力度
+ public void ProssesShake(Vector2 value)
+ {
+ if (value.Length() > _prossesDistance.Length())
+ {
+ _prossesDistance = value;
+ }
+ }
+
+ public void ProssesDirectionalShake(Vector2 value)
+ {
+ _prossesDirectiona += value;
+ }
+
+ ///
+ /// 创建一个抖动, 并设置抖动时间
+ ///
+ /// 抖动力度
+ /// 抖动生效时间
+ public async void CreateShake(Vector2 value, float time)
+ {
+ if (time > 0)
+ {
+ long tempIndex = _index++;
+ SceneTreeTimer sceneTreeTimer = GetTree().CreateTimer(time);
+ _shakeMap[tempIndex] = value;
+ await ToSignal(sceneTreeTimer, "timeout");
+ _shakeMap.Remove(tempIndex);
+ }
+ }
+
+ //抖动调用
+ private void _Shake(float delta)
+ {
+ if (Enable)
+ {
+ var _distance = _CalculateDistance();
+ Offset += new Vector2(
+ (float)GD.RandRange(-_distance.x, _distance.x) - Offset.x,
+ (float)GD.RandRange(-_distance.y, _distance.y) - Offset.y
+ );
+ Offset += _prossesDirectiona;
+ _prossesDistance = Vector2.Zero;
+ _prossesDirectiona = Vector2.Zero;
+ }
+ else
+ {
+ Offset = Offset.LinearInterpolate(Vector2.Zero, RecoveryCoefficient * delta);
+ }
+ }
+
+ //计算相机需要抖动的值
+ private Vector2 _CalculateDistance()
+ {
+ Vector2 temp = Vector2.Zero;
+ float length = 0;
+ foreach (var item in _shakeMap)
+ {
+ var value = item.Value;
+ float tempLenght = value.Length();
+ if (tempLenght > length)
+ {
+ length = tempLenght;
+ temp = value;
+ }
+ }
+ return _prossesDistance.Length() > length ? _prossesDistance : temp;
+ }
+
+}
diff --git a/DungeonShooting_Godot/src/game/common/MathUtils.cs b/DungeonShooting_Godot/src/game/common/MathUtils.cs
new file mode 100644
index 0000000..9c6033a
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/common/MathUtils.cs
@@ -0,0 +1,26 @@
+using Godot;
+
+public static class MathUtils
+{
+ ///
+ /// 返回一个区间内的随机小数
+ ///
+ public static float RandRange(float min, float max)
+ {
+ if (min == max) return min;
+ if (min > max)
+ return GD.Randf() * (min - max) + max;
+ return GD.Randf() * (max - min) + min;
+ }
+
+ ///
+ /// 返回一个区间内的随机整数
+ ///
+ public static int RandRangeInt(int min, int max)
+ {
+ if (min == max) return min;
+ if (min > max)
+ return Mathf.FloorToInt(GD.Randf() * (min - max + 1) + max);
+ return Mathf.FloorToInt(GD.Randf() * (max - min + 1) + min);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/common/NodeExtend.cs b/DungeonShooting_Godot/src/game/common/NodeExtend.cs
new file mode 100644
index 0000000..c1083a9
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/common/NodeExtend.cs
@@ -0,0 +1,107 @@
+using Godot;
+using System;
+
+///
+/// 该类为 node 节点通用扩展函数类
+///
+public static class NodeExtend
+{
+
+ public static ThrowNode StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate)
+ {
+ return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate);
+ }
+
+ public static T StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate) where T : ThrowNode
+ {
+ return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate, null);
+ }
+
+ public static ThrowNode StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Sprite shadowTarget)
+ {
+ return StartThrow(node, size, start, startHeight, direction, xSpeed, ySpeed, rotate, shadowTarget);
+ }
+
+ public static T StartThrow(this Node2D node, Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Sprite shadowTarget) where T : ThrowNode
+ {
+ ThrowNode throwNode = node.GetParentOrNull();
+ T inst;
+ if (throwNode == null)
+ {
+ inst = Activator.CreateInstance();
+ }
+ else if (throwNode is T)
+ {
+ inst = throwNode as T;
+ }
+ else
+ {
+ throwNode.StopThrow();
+ inst = Activator.CreateInstance();
+ }
+ inst.StartThrow(size, start, startHeight, direction, xSpeed, ySpeed, rotate, node, shadowTarget);
+ return inst;
+ }
+
+ ///
+ /// 将一个节点扔到地上, 并设置显示的阴影, 函数返回根据该节点创建的 ThrowNode 节点
+ ///
+ /// 显示的阴影sprite
+ public static ThrowNode PutDown(this Node2D node, Sprite shadowTarget)
+ {
+ return StartThrow(node, Vector2.Zero, node.Position, 0, 0, 0, 0, 0, shadowTarget);
+ }
+
+ ///
+ /// 拾起一个 node 节点, 返回是否拾起成功
+ ///
+ public static bool Pickup(this Node2D node)
+ {
+ ThrowNode parent = node.GetParentOrNull();
+ if (parent != null)
+ {
+ parent.StopThrow();
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 触发扔掉武器操作
+ ///
+ /// 触发扔掉该武器的的角色
+ public static ThrowWeapon StartThrowWeapon(this Weapon weapon, Role master)
+ {
+ if (master.Face == FaceDirection.Left)
+ {
+ weapon.Scale *= new Vector2(1, -1);
+ weapon.RotationDegrees = 180;
+ }
+ var startPos = master.GlobalPosition;// + new Vector2(0, 0);
+ var startHeight = 6;
+ var direction = master.GlobalRotationDegrees + MathUtils.RandRangeInt(-20, 20);
+ var xf = 30;
+ var yf = MathUtils.RandRangeInt(60, 120);
+ var rotate = MathUtils.RandRangeInt(-180, 180);
+ weapon.Position = Vector2.Zero;
+ return weapon.StartThrow(new Vector2(20, 20), startPos, startHeight, direction, xf, yf, rotate, weapon.WeaponSprite);
+ }
+
+
+ ///
+ /// 尝试将一个node2d节点转换成一个 IProp 类
+ ///
+ public static IProp AsProp(this Node2D node2d)
+ {
+ if (node2d is IProp p)
+ {
+ return p;
+ }
+ var parent = node2d.GetParent();
+ if (parent != null && parent is IProp p2)
+ {
+ return p2;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/effect/Cursor.cs b/DungeonShooting_Godot/src/game/effect/Cursor.cs
new file mode 100644
index 0000000..a2d6dd4
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/effect/Cursor.cs
@@ -0,0 +1,60 @@
+using Godot;
+
+///
+/// 鼠标指针
+///
+public class Cursor : Node2D
+{
+
+ private Sprite lt;
+ private Sprite lb;
+ private Sprite rt;
+ private Sprite rb;
+
+ public override void _Ready()
+ {
+ lt = GetNode("LT");
+ lb = GetNode("LB");
+ rt = GetNode("RT");
+ rb = GetNode("RB");
+ }
+
+ public override void _Process(float delta)
+ {
+ var targetGun = RoomManager.Current?.Player?.Holster.ActiveWeapon;
+ if (targetGun != null)
+ {
+ SetScope(targetGun.CurrScatteringRange, targetGun);
+ }
+ else
+ {
+ SetScope(0, targetGun);
+ }
+ SetCursorPos();
+ }
+
+ ///
+ /// 设置光标半径范围
+ ///
+ private void SetScope(float scope, Weapon targetGun)
+ {
+ if (targetGun != null)
+ {
+ var len = GlobalPosition.DistanceTo(targetGun.GlobalPosition);
+ if (targetGun.Attribute != null)
+ {
+ len = Mathf.Max(0, len - targetGun.Attribute.FirePosition.x);
+ }
+ scope = len / GameConfig.ScatteringDistance * scope;
+ }
+ lt.Position = new Vector2(-scope, -scope);
+ lb.Position = new Vector2(-scope, scope);
+ rt.Position = new Vector2(scope, -scope);
+ rb.Position = new Vector2(scope, scope);
+ }
+
+ private void SetCursorPos()
+ {
+ Position = GetGlobalMousePosition();
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/effect/Hit.cs b/DungeonShooting_Godot/src/game/effect/Hit.cs
new file mode 100644
index 0000000..99b4b4d
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/effect/Hit.cs
@@ -0,0 +1,19 @@
+using Godot;
+
+public class Hit : AnimatedSprite
+{
+
+ public override void _Ready()
+ {
+ Frame = 0;
+ Playing = true;
+ }
+
+ ///
+ /// 动画结束, 销毁
+ ///
+ private void _on_Hit_animation_finished()
+ {
+ QueueFree();
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/effect/ThrowNode.cs b/DungeonShooting_Godot/src/game/effect/ThrowNode.cs
new file mode 100644
index 0000000..5073764
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/effect/ThrowNode.cs
@@ -0,0 +1,262 @@
+using Godot;
+
+///
+/// 模拟抛出的物体, 使用时将对象挂载到该节点上即可
+///
+public class ThrowNode : KinematicBody2D
+{
+ ///
+ /// 是否已经结束
+ ///
+ public bool IsOver { get; protected set; } = true;
+ ///
+ /// 物体大小
+ ///
+ public Vector2 Size { get; protected set; }
+ ///
+ /// 起始坐标
+ ///
+ public Vector2 StartPosition { get; protected set; }
+ ///
+ /// 移动方向, 0 - 360
+ ///
+ public float Direction { get; protected set; }
+ ///
+ /// x速度, 也就是水平速度
+ ///
+ public float XSpeed { get; protected set; }
+ ///
+ /// y轴速度, 也就是竖直速度
+ ///
+ public float YSpeed { get; protected set; }
+ ///
+ /// 初始x轴组队
+ ///
+ public float StartXSpeed { get; protected set; }
+ ///
+ /// 初始y轴速度
+ ///
+ public float StartYSpeed { get; protected set; }
+ ///
+ /// 旋转速度
+ ///
+ public float RotateSpeed { get; protected set; }
+ ///
+ /// 挂载的对象
+ ///
+ public Node2D Mount { get; protected set; }
+ ///
+ /// 碰撞组件
+ ///
+ public CollisionShape2D CollisionShape { get; protected set; }
+ ///
+ /// 绘制阴影的精灵
+ ///
+ public Sprite ShadowSprite { get; protected set; }
+
+ protected Sprite ShadowTarget { get; set; }
+
+ private bool inversionX = false;
+
+ public ThrowNode()
+ {
+ Name = "ThrowNode";
+ }
+
+ public override void _Ready()
+ {
+ //只与墙壁碰撞
+ CollisionMask = 1;
+ CollisionLayer = 0;
+ //创建碰撞器
+ if (Size != Vector2.Zero)
+ {
+ CollisionShape = new CollisionShape2D();
+ CollisionShape.Name = "Collision";
+ var shape = new RectangleShape2D();
+ shape.Extents = Size * 0.5f;
+ CollisionShape.Shape = shape;
+ AddChild(CollisionShape);
+ }
+ }
+
+ ///
+ /// 初始化该抛物线对象的基础数据
+ ///
+ /// 抛射的物体所占大小, 用于碰撞检测
+ /// 起始点
+ /// 起始高度
+ /// 角度, 0 - 360
+ /// 横轴速度
+ /// 纵轴速度
+ /// 旋转速度
+ /// 需要挂载的节点
+ /// 抛射的节点显示的纹理, 用于渲染阴影用
+ public virtual void StartThrow(Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Node2D mount)
+ {
+ if (CollisionShape != null)
+ {
+ CollisionShape.Disabled = false;
+ }
+
+ IsOver = false;
+ Size = size;
+ GlobalPosition = StartPosition = start;
+ Direction = direction;
+ XSpeed = xSpeed;
+ YSpeed = ySpeed;
+ StartXSpeed = xSpeed;
+ StartYSpeed = ySpeed;
+ RotateSpeed = rotate;
+
+ if (mount != null)
+ {
+ Mount = mount;
+ var mountParent = mount.GetParent();
+ if (mountParent == null)
+ {
+ AddChild(mount);
+ }
+ else if (mountParent != this)
+ {
+ mountParent.RemoveChild(mount);
+ AddChild(mount);
+ }
+ mount.Position = new Vector2(0, -startHeight);
+ }
+ var parent = GetParent();
+ if (parent == null)
+ {
+ RoomManager.Current.SortRoot.AddChild(this);
+ }
+ else if (parent == RoomManager.Current.SortRoot)
+ {
+ parent.RemoveChild(this);
+ RoomManager.Current.SortRoot.AddChild(this);
+ }
+ }
+
+ ///
+ /// 初始化该抛物线对象的基础数据, 并且渲染阴影
+ ///
+ /// 抛射的物体所占大小, 用于碰撞检测
+ /// 起始点
+ /// 起始高度
+ /// 角度, 0 - 360
+ /// 横轴速度
+ /// 纵轴速度
+ /// 旋转速度
+ /// 需要挂载的节点
+ /// 抛射的节点显示的纹理, 用于渲染阴影用
+ public virtual void StartThrow(Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Node2D mount, Sprite shadowTarget)
+ {
+ StartThrow(size, start, startHeight, direction, xSpeed, ySpeed, rotate, mount);
+ ShadowTarget = shadowTarget;
+ if (shadowTarget != null)
+ {
+ if (ShadowSprite == null)
+ {
+ //阴影
+ ShadowSprite = new Sprite();
+ ShadowSprite.Name = "Shadow";
+ ShadowSprite.ZIndex = -5;
+ ShadowSprite.Material = ResourceManager.ShadowMaterial;
+ AddChild(ShadowSprite);
+ }
+ inversionX = mount.Scale.y < 0 ? true : false;
+ if (inversionX)
+ {
+ ShadowSprite.Scale = shadowTarget.Scale * new Vector2(1, -1);
+ }
+ else
+ {
+ ShadowSprite.Scale = shadowTarget.Scale;
+ }
+ ShadowSprite.Texture = shadowTarget.Texture;
+ }
+ else if (ShadowSprite != null)
+ {
+ ShadowSprite.Texture = null;
+ }
+ }
+
+ ///
+ /// 停止投抛运动
+ ///
+ public Node2D StopThrow()
+ {
+ var mount = Mount;
+ var parent = GetParent();
+ if (parent != null && mount != null)
+ {
+ var gp = mount.GlobalPosition;
+ var gr = mount.GlobalRotation;
+ IsOver = true;
+ Mount = null;
+ RemoveChild(mount);
+ parent.AddChild(mount);
+ mount.GlobalPosition = gp;
+ mount.GlobalRotation = gr;
+ }
+ QueueFree();
+ return mount;
+ }
+
+ ///
+ /// 达到最高点时调用
+ ///
+ protected virtual void OnMaxHeight(float height)
+ {
+
+ }
+
+ ///
+ /// 结束的调用
+ ///
+ protected virtual void OnOver()
+ {
+ GetParent().RemoveChild(this);
+ RoomManager.Current.ObjectRoot.AddChild(this);
+ if (CollisionShape != null)
+ {
+ CollisionShape.Disabled = true;
+ }
+ }
+
+ public override void _Process(float delta)
+ {
+ if (!IsOver)
+ {
+ if (Mount == null)
+ {
+ QueueFree();
+ return;
+ }
+ MoveAndSlide(new Vector2(XSpeed, 0).Rotated(Direction * Mathf.Pi / 180));
+ Mount.Position = new Vector2(0, Mount.Position.y - YSpeed * delta);
+ var rotate = Mount.GlobalRotationDegrees + RotateSpeed * delta;
+ Mount.GlobalRotationDegrees = rotate;
+ if (ShadowSprite != null)
+ {
+ ShadowSprite.GlobalRotationDegrees = rotate;
+ // ShadowSprite.GlobalRotationDegrees = rotate + (inversionX ? 180 : 0);
+ ShadowSprite.GlobalPosition = ShadowTarget.GlobalPosition + new Vector2(0, 1 - Mount.Position.y);
+ }
+ var ysp = YSpeed;
+ YSpeed -= GameConfig.G * delta;
+ //达到最高点
+ if (ysp * YSpeed < 0)
+ {
+ OnMaxHeight(-Mount.Position.y);
+ }
+ //落地判断
+ if (Mount.Position.y >= 0)
+ {
+ Mount.Position = new Vector2(0, 0);
+ IsOver = true;
+ OnOver();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/manager/GameManager.cs b/DungeonShooting_Godot/src/game/manager/GameManager.cs
new file mode 100644
index 0000000..0216d77
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/manager/GameManager.cs
@@ -0,0 +1,19 @@
+using Godot;
+
+///
+/// 游戏主管理器, 自动加载
+///
+public class GameManager : Node2D
+{
+
+ public static GameManager Instance { get; private set; }
+
+ public GameManager()
+ {
+ GameManager.Instance = this;
+
+ //扫描并注册当前程序集下的武器
+ WeaponManager.RegisterWeaponFromAssembly(GetType().Assembly);
+ }
+
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/manager/ResourceManager.cs b/DungeonShooting_Godot/src/game/manager/ResourceManager.cs
new file mode 100644
index 0000000..5421af6
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/manager/ResourceManager.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using Godot;
+
+public static class ResourceManager
+{
+
+ ///
+ /// 2D阴影的材质
+ ///
+ public static ShaderMaterial ShadowMaterial
+ {
+ get
+ {
+ if (_shadowMaterial == null)
+ {
+ _shadowMaterial = ResourceLoader.Load("res://resource/materlal/Shadow.tres");
+ }
+ return _shadowMaterial;
+ }
+ }
+ private static ShaderMaterial _shadowMaterial;
+
+ ///
+ /// 2D阴影的Shader
+ ///
+ public static Shader ShadowShader
+ {
+ get
+ {
+ if (_shadowShader == null)
+ {
+ _shadowShader = ResourceLoader.Load("res://resource/materlal/Shadow.gdshader");
+ }
+ return _shadowShader;
+ }
+ }
+ private static Shader _shadowShader;
+
+ private static readonly Dictionary CachePack = new Dictionary();
+
+ ///
+ /// 加载资源对象, 并且缓存当前资源对象, 可频繁获取
+ ///
+ /// 资源路径
+ public static T Load(string path) where T : class
+ {
+ if (CachePack.TryGetValue(path, out var pack))
+ {
+ return pack as T;
+ }
+ else
+ {
+ pack = ResourceLoader.Load(path);
+ if (pack != null)
+ {
+ CachePack.Add(path, pack);
+ return pack as T;
+ }
+ }
+ return default(T);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/manager/SoundManager.cs b/DungeonShooting_Godot/src/game/manager/SoundManager.cs
new file mode 100644
index 0000000..2cf4f34
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/manager/SoundManager.cs
@@ -0,0 +1,84 @@
+using System;
+using Godot;
+
+///
+/// 音频总线 区分不同的声音 添加声音效果 目前只有背景音乐 和音效 两个bus
+///
+public enum BUS
+{
+ BGM = 0,
+ SFX = 1
+}
+
+///
+/// 声音管理 背景音乐管理 音效
+///
+public class SoundManager
+{
+ public static SoundManager Instance { get => SingleTon.singleTon; }
+
+ private static class SingleTon
+ {
+ internal static SoundManager singleTon = new SoundManager();
+ }
+
+ private static AudioStreamPlayer audioStreamPlayer = new AudioStreamPlayer();
+
+
+ ///
+ /// 背景音乐路径
+ ///
+ public static string BGMPath = "res://resource/sound/bgm/";
+ ///
+ /// 音效路径
+ ///
+ public static string SFXPath = "res://resource/sound/sfx/";
+
+ ///
+ /// 播放声音 用于bgm
+ ///
+ /// bgm名字 在resource/sound/bgm/目录下
+ /// 需要播放声音得节点 将成为音频播放节点的父节点
+ /// 音量
+ public static void PlayeMusic(string soundName, Node node, float volume)
+ {
+ AudioStream sound = ResourceManager.Load(BGMPath + soundName);
+ if (sound != null)
+ {
+ AudioStreamPlayer soundNode = new AudioStreamPlayer();
+ node.AddChild(soundNode);
+ soundNode.Stream = sound;
+ soundNode.Bus = Enum.GetName(typeof(BUS), 0);
+ soundNode.VolumeDb = volume;
+ soundNode.Play();
+ }
+ else
+ {
+ GD.Print("没有这个资源!!!");
+ }
+ }
+ ///
+ /// 添加并播放音效 用于音效
+ ///
+ /// 音效文件名字 在resource/sound/sfx/目录下
+ /// 需要播放声音得节点 将成为音频播放节点的父节点
+ /// 音量
+ public static void PlaySoundEffect(string soundName, Node node, float volume = 0f)
+ {
+ AudioStream sound = ResourceManager.Load(SFXPath + soundName);
+ if (sound != null)
+ {
+ AudioStreamPlayer soundNode = new AudioStreamPlayer();
+ node.AddChild(soundNode);
+ soundNode.Stream = sound;
+ soundNode.Bus = Enum.GetName(typeof(BUS), 1);
+ soundNode.VolumeDb = volume;
+ soundNode.Play();
+ GD.Print("bus:", soundNode.Bus);
+ }
+ else
+ {
+ GD.Print("没有这个资源!!!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/CheckInteractiveResult.cs b/DungeonShooting_Godot/src/game/props/CheckInteractiveResult.cs
new file mode 100644
index 0000000..07053f5
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/CheckInteractiveResult.cs
@@ -0,0 +1,28 @@
+
+///
+/// 检测互动返回的数据集
+///
+public class CheckInteractiveResult
+{
+ ///
+ /// 互动物体
+ ///
+ public IProp Target;
+ ///
+ /// 是否可以互动
+ ///
+ public bool CanInteractive;
+ ///
+ /// 互动提示信息
+ ///
+ public string Message;
+ ///
+ /// 互动提示显示的图标
+ ///
+ public string ShowIcon;
+
+ public CheckInteractiveResult(IProp target)
+ {
+ Target = target;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/IProp.cs b/DungeonShooting_Godot/src/game/props/IProp.cs
new file mode 100644
index 0000000..f17b5f4
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/IProp.cs
@@ -0,0 +1,24 @@
+using Godot;
+
+///
+/// 道具接口
+///
+public interface IProp
+{
+ ///
+ /// 获取道具所在的坐标
+ ///
+ Vector2 GetItemPosition();
+
+ ///
+ /// 返回是否能互动
+ ///
+ /// 触发者
+ CheckInteractiveResult CheckInteractive(Role master);
+
+ ///
+ /// 与角色互动时调用
+ ///
+ /// 触发者
+ void Interactive(Role master);
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/package/Holster.cs b/DungeonShooting_Godot/src/game/props/package/Holster.cs
new file mode 100644
index 0000000..bbe7c9b
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/package/Holster.cs
@@ -0,0 +1,272 @@
+using Godot;
+
+///
+/// 角色身上的武器袋, 存储角色携带的武器
+///
+public class Holster
+{
+ ///
+ /// 插槽类
+ ///
+ public class WeaponSlot
+ {
+ ///
+ /// 是否启用
+ ///
+ public bool Enable = false;
+ ///
+ /// 当前插槽存放的武器类型
+ ///
+ public WeaponWeightType Type = WeaponWeightType.MainWeapon;
+ ///
+ /// 插槽存放的武器
+ ///
+ public Weapon Weapon;
+ }
+
+ ///
+ /// 归属者
+ ///
+ public Role Master { get; }
+
+ ///
+ /// 当前使用的武器对象
+ ///
+ public Weapon ActiveWeapon { get; private set; }
+
+ ///
+ /// 当前使用的武器的索引
+ ///
+ public int ActiveIndex { get; private set; } = 0;
+
+ public WeaponSlot[] SlotList { get; } = new WeaponSlot[4];
+
+ public Holster(Role master)
+ {
+ Master = master;
+
+ //创建武器的插槽, 默认前两个都是启用的
+ WeaponSlot slot1 = new WeaponSlot();
+ slot1.Enable = true;
+ SlotList[0] = slot1;
+
+ WeaponSlot slot2 = new WeaponSlot();
+ slot2.Enable = true;
+ slot2.Type = WeaponWeightType.DeputyWeapon;
+ SlotList[1] = slot2;
+
+ WeaponSlot slot3 = new WeaponSlot();
+ SlotList[2] = slot3;
+
+ WeaponSlot slot4 = new WeaponSlot();
+ slot4.Type = WeaponWeightType.DeputyWeapon;
+ SlotList[3] = slot4;
+ }
+
+ ///
+ /// 根据索引获取武器
+ ///
+ public Weapon GetWeapon(int index) {
+ if (index >= SlotList.Length)
+ {
+ return null;
+ }
+ return SlotList[index].Weapon;
+ }
+
+ ///
+ /// 根据武器id查找武器袋中该武器所在的位置, 如果没有, 则返回 -1
+ ///
+ /// 武器id
+ public int FindWeapon(string id)
+ {
+ for (int i = 0; i < SlotList.Length; i++)
+ {
+ var item = SlotList[i];
+ if (item.Weapon != null && item.Weapon.Id == id)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ ///
+ /// 返回是否能放入武器
+ ///
+ /// 武器对象
+ public bool CanPickupWeapon(Weapon weapon)
+ {
+ for (int i = 0; i < SlotList.Length; i++)
+ {
+ var item = SlotList[i];
+ if (item.Enable && weapon.Attribute.WeightType == item.Type && item.Weapon == null)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// 拾起武器, 存入武器袋中, 返回存放在武器袋的位置, 如果容不下这把武器, 则会返回 -1
+ ///
+ /// 武器对象
+ public int PickupWeapon(Weapon weapon)
+ {
+ for (int i = 0; i < SlotList.Length; i++)
+ {
+ var item = SlotList[i];
+ if (item.Enable && weapon.Attribute.WeightType == item.Type && item.Weapon == null)
+ {
+ weapon.Pickup();
+ item.Weapon = weapon;
+ ExchangeByIndex(i);
+ weapon._PickUpWeapon(Master);
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ ///
+ /// 移除指定位置的武器, 并返回这个武器对象, 如果移除正在使用的这把武器, 则会自动切换到上一把武器
+ ///
+ /// 所在武器袋的位置索引
+ public Weapon RmoveWeapon(int index)
+ {
+ if (index < 0 || index >= SlotList.Length)
+ {
+ return null;
+ }
+ var slot = SlotList[index];
+ if (slot.Weapon == null)
+ {
+ return null;
+ }
+ var weapon = slot.Weapon;
+ weapon.GetParent().RemoveChild(weapon);
+ slot.Weapon = null;
+
+ //如果是当前手持的武器, 就需要调用切换武器操作
+ if (index == ActiveIndex)
+ {
+ //没有其他武器了
+ if (ExchangePrev() == index)
+ {
+ ActiveIndex = 0;
+ ActiveWeapon = null;
+ }
+ }
+ weapon._ThrowOutWeapon();
+ return weapon;
+ }
+
+ ///
+ /// 切换到上一个武器
+ ///
+ public int ExchangePrev()
+ {
+ var index = ActiveIndex - 1;
+ do
+ {
+ if (index < 0)
+ {
+ index = SlotList.Length - 1;
+ }
+ if (ExchangeByIndex(index))
+ {
+ return index;
+ }
+ } while (index-- != ActiveIndex);
+ return -1;
+ }
+
+ ///
+ /// 切换到下一个武器,
+ ///
+ public int ExchangeNext()
+ {
+ var index = ActiveIndex + 1;
+ do
+ {
+ if (index >= SlotList.Length)
+ {
+ index = 0;
+ }
+ if (ExchangeByIndex(index))
+ {
+ return index;
+ }
+ }
+ while (index++ != ActiveIndex);
+ return -1;
+ }
+
+ ///
+ /// 切换到指定索引的武器
+ ///
+ public bool ExchangeByIndex(int index)
+ {
+ if (index == ActiveIndex && ActiveWeapon != null) return true;
+ if (index < 0 || index > SlotList.Length) return false;
+ var slot = SlotList[index];
+ if (slot == null || slot.Weapon == null) return false;
+
+ //将上一把武器放到背后
+ if (ActiveWeapon != null)
+ {
+ var tempParent = ActiveWeapon.GetParentOrNull();
+ if (tempParent != null)
+ {
+ tempParent.RemoveChild(ActiveWeapon);
+ Master.BackMountPoint.AddChild(ActiveWeapon);
+ if (ActiveIndex == 0)
+ {
+ ActiveWeapon.Position = new Vector2(0, 5);
+ ActiveWeapon.RotationDegrees = 50;
+ ActiveWeapon.Scale = new Vector2(-1, 1);
+ }
+ else if (ActiveIndex == 1)
+ {
+ ActiveWeapon.Position = new Vector2(0, 0);
+ ActiveWeapon.RotationDegrees = 120;
+ ActiveWeapon.Scale = new Vector2(1, -1);
+ }
+ else if (ActiveIndex == 2)
+ {
+ ActiveWeapon.Position = new Vector2(0, 5);
+ ActiveWeapon.RotationDegrees = 310;
+ ActiveWeapon.Scale = new Vector2(1, 1);
+ }
+ else if (ActiveIndex == 3)
+ {
+ ActiveWeapon.Position = new Vector2(0, 0);
+ ActiveWeapon.RotationDegrees = 60;
+ ActiveWeapon.Scale = new Vector2(1, 1);
+ }
+ ActiveWeapon._Conceal();
+ }
+ }
+
+ //更改父节点
+ var parent = slot.Weapon.GetParentOrNull();
+ if (parent == null)
+ {
+ Master.MountPoint.AddChild(slot.Weapon);
+ }
+ else if (parent != Master.MountPoint)
+ {
+ parent.RemoveChild(slot.Weapon);
+ Master.MountPoint.AddChild(slot.Weapon);
+ }
+
+ slot.Weapon.Position = Vector2.Zero;
+ slot.Weapon.Scale = Vector2.One;
+ slot.Weapon.RotationDegrees = 0;
+ ActiveWeapon = slot.Weapon;
+ ActiveIndex = index;
+ ActiveWeapon._Active();
+ return true;
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/props/weapon/RegisterWeapon.cs b/DungeonShooting_Godot/src/game/props/weapon/RegisterWeapon.cs
new file mode 100644
index 0000000..3cbcd57
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/RegisterWeapon.cs
@@ -0,0 +1,23 @@
+using System;
+
+///
+/// 用作 Weapon 子类上, 用于注册武器, 允许同时存在多个 RegisterWeapon 特性
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class RegisterWeapon : Attribute
+{
+
+ public string Id { get; private set; }
+ public Type AttributeType { get; private set; }
+
+ public RegisterWeapon(string id)
+ {
+ Id = id;
+ }
+
+ public RegisterWeapon(string id, Type attributeType)
+ {
+ Id = id;
+ AttributeType = attributeType;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/RegisterWeaponFunction.cs b/DungeonShooting_Godot/src/game/props/weapon/RegisterWeaponFunction.cs
new file mode 100644
index 0000000..1796a95
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/RegisterWeaponFunction.cs
@@ -0,0 +1,16 @@
+using System;
+
+///
+/// 用作静态函数上, 用于注册武器, 函数必须返回一个 Weapon 对象, 且参数为 string id,
+/// 那么它看起来应该像这样: static Weapon Method(string id);
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+public class RegisterWeaponFunction : Attribute
+{
+ public string Id { get; private set; }
+
+ public RegisterWeaponFunction(string id)
+ {
+ Id = id;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/ThrowGun.cs b/DungeonShooting_Godot/src/game/props/weapon/ThrowGun.cs
new file mode 100644
index 0000000..d3c652a
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/ThrowGun.cs
@@ -0,0 +1,44 @@
+using Godot;
+
+public class ThrowWeapon : ThrowNode
+{
+
+ private bool fristOver = true;
+
+ public override void _Ready()
+ {
+ base._Ready();
+ ZIndex = 2;
+ }
+
+ public override void StartThrow(Vector2 size, Vector2 start, float startHeight, float direction, float xSpeed, float ySpeed, float rotate, Node2D mount)
+ {
+ base.StartThrow(size, start, startHeight, direction, xSpeed, ySpeed, rotate, mount);
+ fristOver = true;
+ }
+
+ protected override void OnOver()
+ {
+ if (fristOver)
+ {
+ fristOver = false;
+ if (Mount is Weapon gun)
+ {
+ gun._FallToGround();
+ }
+ }
+ //如果落地高度不够低, 再抛一次
+ if (StartYSpeed > 1)
+ {
+ StartThrow(Size, GlobalPosition, 0, Direction, XSpeed * 0.8f, StartYSpeed * 0.5f, RotateSpeed * 0.5f, null);
+ }
+ else //结束
+ {
+ base.OnOver();
+ }
+ }
+ protected override void OnMaxHeight(float height)
+ {
+ ZIndex = 0;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/props/weapon/Weapon.cs
new file mode 100644
index 0000000..25c1b6f
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/Weapon.cs
@@ -0,0 +1,773 @@
+using Godot;
+using System;
+
+///
+/// 武器的基类
+///
+public abstract class Weapon : Node2D, IProp
+{
+
+ ///
+ /// 武器的唯一id
+ ///
+ public string Id { get; }
+
+ ///
+ /// 开火回调事件
+ ///
+ public event Action FireEvent;
+
+ ///
+ /// 属性数据
+ ///
+ public WeaponAttribute Attribute { get; private set; }
+
+ ///
+ /// 武器的图片
+ ///
+ public Sprite WeaponSprite { get; private set; }
+
+ ///
+ /// 动画播放器
+ ///
+ ///
+ public AnimationPlayer AnimationPlayer { get; private set; }
+
+ ///
+ /// 武器攻击的目标阵营
+ ///
+ public CampEnum TargetCamp { get; set; }
+
+ ///
+ /// 该武器的拥有者
+ ///
+ public Role Master { get; private set; }
+
+ ///
+ /// 当前弹夹弹药剩余量
+ ///
+ public int CurrAmmo { get; private set; }
+ ///
+ /// 剩余弹药量
+ ///
+ public int ResidueAmmo { get; private set; }
+
+ ///
+ /// 武器管的开火点
+ ///
+ public Position2D FirePoint { get; private set; }
+ ///
+ /// 武器管的原点
+ ///
+ public Position2D OriginPoint { get; private set; }
+ ///
+ /// 弹壳抛出的点
+ ///
+ public Position2D ShellPoint { get; private set; }
+ ///
+ /// 碰撞器节点
+ ///
+ ///
+ public CollisionShape2D CollisionShape2D { get; private set; }
+ ///
+ /// 武器的当前散射半径
+ ///
+ public float CurrScatteringRange { get; private set; } = 0;
+ ///
+ /// 是否在换弹中
+ ///
+ ///
+ public bool Reloading { get; private set; } = false;
+ ///
+ /// 换弹计时器
+ ///
+ public float ReloadTimer { get; private set; } = 0;
+ ///
+ /// 换弹进度 (0 - 1)
+ ///
+ public float ReloadProgress
+ {
+ get
+ {
+ if (Attribute.AloneReload)
+ {
+ var num = 1f / Attribute.AmmoCapacity;
+ return num * (Attribute.AmmoCapacity - CurrAmmo - 1) + num * (ReloadTimer / Attribute.ReloadTime);
+ }
+ return ReloadTimer / Attribute.ReloadTime;
+ }
+ }
+
+ //是否按下
+ private bool triggerFlag = false;
+ //扳机计时器
+ private float triggerTimer = 0;
+ //开火前延时时间
+ private float delayedTime = 0;
+ //开火间隙时间
+ private float fireInterval = 0;
+ //开火武器口角度
+ private float fireAngle = 0;
+ //攻击冷却计时
+ private float attackTimer = 0;
+ //攻击状态
+ private bool attackFlag = false;
+ //按下的时间
+ private float downTimer = 0;
+ //松开的时间
+ private float upTimer = 0;
+ //连发次数
+ private float continuousCount = 0;
+ //连发状态记录
+ private bool continuousShootFlag = false;
+
+ ///
+ /// 根据属性创建一把武器
+ ///
+ /// 武器唯一id
+ /// 属性
+ public Weapon(string id, WeaponAttribute attribute)
+ {
+ Id = id;
+ Attribute = attribute;
+ //加载预制体
+ var tempPrefab = ResourceManager.Load(Attribute.WeaponPrefab);
+ if (tempPrefab == null)
+ {
+ throw new Exception("WeaponAttribute中未设置'WeaponPrefab'属性!");
+ }
+ var tempNode = tempPrefab.Instance();
+ var body = tempNode.GetChild(0);
+ tempNode.RemoveChild(body);
+ AddChild(body);
+
+ WeaponSprite = GetNode("WeaponBody/WeaponSprite");
+ FirePoint = GetNode("WeaponBody/FirePoint");
+ OriginPoint = GetNode("WeaponBody/OriginPoint");
+ ShellPoint = GetNode("WeaponBody/ShellPoint");
+ AnimationPlayer = GetNode("WeaponBody/AnimationPlayer");
+ CollisionShape2D = GetNode("WeaponBody/Collision");
+
+ //更新图片
+ WeaponSprite.Texture = ResourceLoader.Load(Attribute.Sprite);
+ WeaponSprite.Position = Attribute.CenterPosition;
+ //开火位置
+ FirePoint.Position = new Vector2(Attribute.FirePosition.x, -Attribute.FirePosition.y);
+ OriginPoint.Position = new Vector2(0, -Attribute.FirePosition.y);
+
+ //弹药量
+ CurrAmmo = Attribute.AmmoCapacity;
+ //剩余弹药量
+ ResidueAmmo = Attribute.MaxAmmoCapacity - Attribute.AmmoCapacity;
+ }
+
+ ///
+ /// 当按下扳机时调用
+ ///
+ protected abstract void OnDownTrigger();
+
+ ///
+ /// 当松开扳机时调用
+ ///
+ protected abstract void OnUpTrigger();
+
+ ///
+ /// 单次开火时调用的函数
+ ///
+ protected abstract void OnFire();
+
+ ///
+ /// 发射子弹时调用的函数, 每发射一枚子弹调用一次,
+ /// 如果做霰弹武器效果, 一次开火发射5枚子弹, 则该函数调用5次
+ ///
+ protected abstract void OnShoot();
+
+ ///
+ /// 当开始换弹时调用
+ ///
+ protected abstract void OnReload();
+
+ ///
+ /// 当换弹完成时调用
+ ///
+ protected abstract void OnReloadFinish();
+
+ ///
+ /// 当武器被拾起时调用
+ ///
+ /// 拾起该武器的角色
+ protected abstract void OnPickUp(Role master);
+
+ ///
+ /// 当武器从武器袋中扔掉时调用
+ ///
+ protected abstract void OnThrowOut();
+
+ ///
+ /// 当武器被激活时调用, 也就是使用当武器是调用
+ ///
+ protected abstract void OnActive();
+
+ ///
+ /// 当武器被收起时调用
+ ///
+ protected abstract void OnConceal();
+
+ public override void _Process(float delta)
+ {
+ if (Master == null) //这把武器被扔在地上
+ {
+ Reloading = false;
+ ReloadTimer = 0;
+ triggerTimer = triggerTimer > 0 ? triggerTimer - delta : 0;
+ triggerFlag = false;
+ attackFlag = false;
+ attackTimer = attackTimer > 0 ? attackTimer - delta : 0;
+ CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta, Attribute.StartScatteringRange);
+ continuousCount = 0;
+ delayedTime = 0;
+ }
+ else if (Master.Holster.ActiveWeapon != this) //当前武器没有被使用
+ {
+ Reloading = false;
+ ReloadTimer = 0;
+ triggerTimer = triggerTimer > 0 ? triggerTimer - delta : 0;
+ triggerFlag = false;
+ attackFlag = false;
+ attackTimer = attackTimer > 0 ? attackTimer - delta : 0;
+ CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta, Attribute.StartScatteringRange);
+ continuousCount = 0;
+ delayedTime = 0;
+ }
+ else //正在使用中
+ {
+
+ //换弹
+ if (Reloading)
+ {
+ ReloadTimer -= delta;
+ if (ReloadTimer <= 0)
+ {
+ ReloadSuccess();
+ }
+ }
+
+ if (triggerFlag)
+ {
+ if (upTimer > 0) //第一帧按下扳机
+ {
+ upTimer = 0;
+ DownTrigger();
+ }
+ downTimer += delta;
+ }
+ else
+ {
+ if (downTimer > 0) //第一帧松开扳机
+ {
+ downTimer = 0;
+ UpTriggern();
+ }
+ upTimer += delta;
+ }
+
+ // 攻击的计时器
+ if (attackTimer > 0)
+ {
+ attackTimer -= delta;
+ if (attackTimer < 0)
+ {
+ delayedTime += attackTimer;
+ attackTimer = 0;
+ }
+ }
+ else if (delayedTime > 0) //攻击延时
+ {
+ delayedTime -= delta;
+ if (attackTimer < 0)
+ {
+ delayedTime = 0;
+ }
+ }
+
+ //连发判断
+ if (continuousCount > 0 && delayedTime <= 0 && attackTimer <= 0)
+ {
+ //开火
+ TriggernFire();
+ }
+
+ if (!attackFlag && attackTimer <= 0)
+ {
+ CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta, Attribute.StartScatteringRange);
+ }
+ triggerTimer = triggerTimer > 0 ? triggerTimer - delta : 0;
+ triggerFlag = false;
+ attackFlag = false;
+
+ //武器身回归
+ Position = Position.MoveToward(Vector2.Zero, 35 * delta);
+ if (fireInterval == 0)
+ {
+ RotationDegrees = 0;
+ }
+ else
+ {
+ RotationDegrees = Mathf.Lerp(0, fireAngle, attackTimer / fireInterval);
+ }
+ }
+ }
+
+ ///
+ /// 扳机函数, 调用即视为扣动扳机
+ ///
+ public void Trigger()
+ {
+ //是否第一帧按下
+ var justDown = downTimer == 0;
+ //是否能发射
+ var flag = false;
+ if (continuousCount <= 0) //不能处于连发状态下
+ {
+ if (Attribute.ContinuousShoot) //自动射击
+ {
+ if (triggerTimer > 0)
+ {
+ if (continuousShootFlag)
+ {
+ flag = true;
+ }
+ }
+ else
+ {
+ flag = true;
+ if (delayedTime <= 0 && attackTimer <= 0)
+ {
+ continuousShootFlag = true;
+ }
+ }
+ }
+ else //半自动
+ {
+ if (justDown && triggerTimer <= 0)
+ {
+ flag = true;
+ }
+ }
+ }
+
+ if (flag)
+ {
+ var fireFlag = true; //是否能开火
+ if (Reloading) //换弹中
+ {
+ if (CurrAmmo > 0 && Attribute.AloneReload && Attribute.AloneReloadCanShoot) //立即停止换弹
+ {
+ Reloading = false;
+ ReloadTimer = 0;
+ }
+ else
+ {
+ fireFlag = false;
+ }
+ }
+ else if (CurrAmmo <= 0)
+ {
+ fireFlag = false;
+ //子弹不够
+ _Reload();
+ }
+
+ if (fireFlag)
+ {
+ if (justDown)
+ {
+ //开火前延时
+ delayedTime = Attribute.DelayedTime;
+ //扳机按下间隔
+ triggerTimer = Attribute.TriggerInterval;
+ //连发数量
+ if (!Attribute.ContinuousShoot)
+ {
+ continuousCount = MathUtils.RandRangeInt(Attribute.MinContinuousCount, Attribute.MaxContinuousCount);
+ }
+ }
+ if (delayedTime <= 0 && attackTimer <= 0)
+ {
+ TriggernFire();
+ }
+ attackFlag = true;
+ }
+
+ }
+ triggerFlag = true;
+ }
+
+ ///
+ /// 刚按下扳机
+ ///
+ private void DownTrigger()
+ {
+ OnDownTrigger();
+ }
+
+ ///
+ /// 刚松开扳机
+ ///
+ private void UpTriggern()
+ {
+ continuousShootFlag = false;
+ if (delayedTime > 0)
+ {
+ continuousCount = 0;
+ }
+ OnUpTrigger();
+ }
+
+ ///
+ /// 触发开火
+ ///
+ private void TriggernFire()
+ {
+ continuousCount = continuousCount > 0 ? continuousCount - 1 : 0;
+
+ //减子弹数量
+ CurrAmmo--;
+ //开火间隙
+ fireInterval = 60 / Attribute.StartFiringSpeed;
+ //攻击冷却
+ attackTimer += fireInterval;
+
+ //触发开火函数
+ OnFire();
+
+ //开火发射的子弹数量
+ var bulletCount = MathUtils.RandRangeInt(Attribute.MaxFireBulletCount, Attribute.MinFireBulletCount);
+ //武器口角度
+ var angle = new Vector2(GameConfig.ScatteringDistance, CurrScatteringRange).Angle();
+
+ //创建子弹
+ for (int i = 0; i < bulletCount; i++)
+ {
+ //先算武器口方向
+ Rotation = (float)GD.RandRange(-angle, angle);
+ //发射子弹
+ OnShoot();
+ }
+
+ //当前的散射半径
+ CurrScatteringRange = Mathf.Min(CurrScatteringRange + Attribute.ScatteringRangeAddValue, Attribute.FinalScatteringRange);
+ //武器的旋转角度
+ RotationDegrees -= Attribute.UpliftAngle;
+ fireAngle = RotationDegrees;
+ //武器身位置
+ Position = new Vector2(Mathf.Max(-Attribute.MaxBacklash, Position.x - MathUtils.RandRange(Attribute.MinBacklash, Attribute.MaxBacklash)), Position.y);
+
+ if (FireEvent != null)
+ {
+ FireEvent(this);
+ }
+ }
+
+ ///
+ /// 返回弹药是否到达上限
+ ///
+ public bool IsFullAmmo()
+ {
+ return CurrAmmo + ResidueAmmo >= Attribute.MaxAmmoCapacity;
+ }
+
+ ///
+ /// 返回是否弹药耗尽
+ ///
+ public bool IsEmptyAmmo()
+ {
+ return CurrAmmo + ResidueAmmo == 0;
+ }
+
+ ///
+ /// 拾起的弹药数量, 如果到达容量上限, 则返回拾取完毕后剩余的弹药数量
+ ///
+ /// 弹药数量
+ public int PickUpAmmo(int count)
+ {
+ var num = ResidueAmmo;
+ ResidueAmmo = Mathf.Min(ResidueAmmo + count, Attribute.MaxAmmoCapacity - CurrAmmo);
+ return count - ResidueAmmo + num;
+ }
+
+ ///
+ /// 触发换弹
+ ///
+ public void _Reload()
+ {
+ if (CurrAmmo < Attribute.AmmoCapacity && ResidueAmmo > 0 && !Reloading)
+ {
+ Reloading = true;
+ ReloadTimer = Attribute.ReloadTime;
+ OnReload();
+ }
+ }
+
+ ///
+ /// 换弹计时器时间到, 执行换弹操作
+ ///
+ private void ReloadSuccess()
+ {
+ if (Attribute.AloneReload) //单独装填
+ {
+ if (ResidueAmmo >= Attribute.AloneReloadCount) //剩余子弹充足
+ {
+ if (CurrAmmo + Attribute.AloneReloadCount <= Attribute.AmmoCapacity)
+ {
+ ResidueAmmo -= Attribute.AloneReloadCount;
+ CurrAmmo += Attribute.AloneReloadCount;
+ }
+ else //子弹满了
+ {
+ var num = Attribute.AmmoCapacity - CurrAmmo;
+ CurrAmmo = Attribute.AmmoCapacity;
+ ResidueAmmo -= num;
+ }
+ }
+ else if (ResidueAmmo != 0) //剩余子弹不足
+ {
+ if (ResidueAmmo + CurrAmmo <= Attribute.AmmoCapacity)
+ {
+ CurrAmmo += ResidueAmmo;
+ ResidueAmmo = 0;
+ }
+ else //子弹满了
+ {
+ var num = Attribute.AmmoCapacity - CurrAmmo;
+ CurrAmmo = Attribute.AmmoCapacity;
+ ResidueAmmo -= num;
+ }
+ }
+ if (ResidueAmmo == 0 || CurrAmmo == Attribute.AmmoCapacity) //换弹结束
+ {
+ Reloading = false;
+ ReloadTimer = 0;
+ OnReloadFinish();
+ }
+ else
+ {
+ ReloadTimer = Attribute.ReloadTime;
+ OnReload();
+ }
+ }
+ else //换弹结束
+ {
+ if (ResidueAmmo >= Attribute.AmmoCapacity)
+ {
+ ResidueAmmo -= Attribute.AmmoCapacity - CurrAmmo;
+ CurrAmmo = Attribute.AmmoCapacity;
+ }
+ else
+ {
+ CurrAmmo = ResidueAmmo;
+ ResidueAmmo = 0;
+ }
+ Reloading = false;
+ ReloadTimer = 0;
+ OnReloadFinish();
+ }
+ }
+
+ public CheckInteractiveResult CheckInteractive(Role master)
+ {
+ var result = new CheckInteractiveResult(this);
+ if (Master == null)
+ {
+ var masterWeapon = master.Holster.ActiveWeapon;
+ //查找是否有同类型武器
+ var index = master.Holster.FindWeapon(Id);
+ if (index != -1) //如果有这个武器
+ {
+ if (CurrAmmo + ResidueAmmo != 0) //子弹不为空
+ {
+ var targetWeapon = master.Holster.GetWeapon(index);
+ if (!targetWeapon.IsFullAmmo()) //背包里面的武器子弹未满
+ {
+ //可以互动拾起弹药
+ result.CanInteractive = true;
+ result.Message = Attribute.Name;
+ result.ShowIcon = "res://resource/sprite/ui/icon/icon_bullet.png";
+ return result;
+ }
+ }
+ }
+ else //没有武器
+ {
+ if (master.Holster.CanPickupWeapon(this)) //能拾起武器
+ {
+ //可以互动, 拾起武器
+ result.CanInteractive = true;
+ result.Message = Attribute.Name;
+ result.ShowIcon = "res://resource/sprite/ui/icon/icon_pickup.png";
+ return result;
+ }
+ else if (masterWeapon != null && masterWeapon.Attribute.WeightType == Attribute.WeightType) //替换武器
+ {
+ //可以互动, 切换武器
+ result.CanInteractive = true;
+ result.Message = Attribute.Name;
+ result.ShowIcon = "res://resource/sprite/ui/icon/icon_replace.png";
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
+ public void Interactive(Role master)
+ {
+ //查找是否有同类型武器
+ var index = master.Holster.FindWeapon(Id);
+ if (index != -1) //如果有这个武器
+ {
+ if (CurrAmmo + ResidueAmmo == 0) //没有子弹了
+ {
+ return;
+ }
+ var weapon = master.Holster.GetWeapon(index);
+ //子弹上限
+ var maxCount = Attribute.MaxAmmoCapacity;
+ //是否捡到子弹
+ var flag = false;
+ if (ResidueAmmo > 0 && weapon.CurrAmmo + weapon.ResidueAmmo < maxCount)
+ {
+ var count = weapon.PickUpAmmo(ResidueAmmo);
+ if (count != ResidueAmmo)
+ {
+ ResidueAmmo = count;
+ flag = true;
+ }
+ }
+ if (CurrAmmo > 0 && weapon.CurrAmmo + weapon.ResidueAmmo < maxCount)
+ {
+ var count = weapon.PickUpAmmo(CurrAmmo);
+ if (count != CurrAmmo)
+ {
+ CurrAmmo = count;
+ flag = true;
+ }
+ }
+ //播放互动效果
+ if (flag)
+ {
+ this.StartThrow(new Vector2(20, 20), GlobalPosition, 0, 0,
+ MathUtils.RandRangeInt(-20, 20), MathUtils.RandRangeInt(20, 50),
+ MathUtils.RandRangeInt(-180, 180), WeaponSprite);
+ }
+ }
+ else //没有武器
+ {
+ if (master.Holster.PickupWeapon(this) == -1)
+ {
+ var slot = master.Holster.SlotList[master.Holster.ActiveIndex];
+ if (slot.Type == Attribute.WeightType)
+ {
+ var weapon = master.Holster.RmoveWeapon(master.Holster.ActiveIndex);
+ weapon.StartThrowWeapon(master);
+ master.PickUpWeapon(this);
+ }
+ }
+ }
+ }
+
+ public Vector2 GetItemPosition()
+ {
+ return GlobalPosition;
+ }
+
+ ///
+ /// 触发落到地面
+ ///
+ public void _FallToGround()
+ {
+ //启用碰撞
+ CollisionShape2D.Disabled = false;
+ }
+
+ ///
+ /// 触发拾起
+ ///
+ public void _PickUpWeapon(Role master)
+ {
+ Master = master;
+ //握把位置
+ WeaponSprite.Position = Attribute.HoldPosition;
+ //清除泛白效果
+ ShaderMaterial sm = WeaponSprite.Material as ShaderMaterial;
+ sm.SetShaderParam("schedule", 0);
+ //停止动画
+ AnimationPlayer.Stop();
+ ZIndex = 0;
+ //禁用碰撞
+ CollisionShape2D.Disabled = true;
+ OnPickUp(master);
+ }
+
+ ///
+ /// 触发抛出
+ /// a
+ public void _ThrowOutWeapon()
+ {
+ Master = null;
+ WeaponSprite.Position = Attribute.CenterPosition;
+ AnimationPlayer.Play("Floodlight");
+ OnThrowOut();
+ }
+
+ ///
+ /// 触发启用武器
+ ///
+ public void _Active()
+ {
+ OnActive();
+ }
+
+ ///
+ /// 触发收起武器
+ ///
+ public void _Conceal()
+ {
+ OnConceal();
+ }
+
+ ///
+ /// 实例化并返回子弹对象
+ ///
+ /// 子弹的预制体
+ protected T CreateBullet(PackedScene bulletPack, Vector2 globalPostion, float globalRotation, Node parent = null) where T : Node2D, IBullet
+ {
+ return (T)CreateBullet(bulletPack, globalPostion, globalRotation, parent);
+ }
+
+ ///
+ /// 实例化并返回子弹对象
+ ///
+ /// 子弹的预制体
+ protected IBullet CreateBullet(PackedScene bulletPack, Vector2 globalPostion, float globalRotation, Node parent = null)
+ {
+ // 实例化子弹
+ Node2D bullet = bulletPack.Instance();
+ // 设置坐标
+ bullet.GlobalPosition = globalPostion;
+ // 旋转角度
+ bullet.GlobalRotation = globalRotation;
+ if (parent == null)
+ {
+ RoomManager.Current.SortRoot.AddChild(bullet);
+ }
+ else
+ {
+ parent.AddChild(bullet);
+ }
+ // 调用初始化
+ IBullet result = (IBullet)bullet;
+ result.Init(TargetCamp, this, null);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/WeaponAttribute.cs b/DungeonShooting_Godot/src/game/props/weapon/WeaponAttribute.cs
new file mode 100644
index 0000000..c6cad38
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/WeaponAttribute.cs
@@ -0,0 +1,152 @@
+using Godot;
+
+///
+/// 武器上的属性
+///
+public class WeaponAttribute
+{
+ ///
+ /// 武器显示的名称
+ ///
+ public string Name = "Gun1";
+ ///
+ /// 武器 Prefab, 必须继承场景 "res://prefab/weapon/Weapon.tscn"
+ ///
+ public string WeaponPrefab = "res://prefab/weapon/Weapon.tscn";
+ ///
+ /// 武器类型
+ ///
+ public WeaponWeightType WeightType = WeaponWeightType.MainWeapon;
+ ///
+ /// 武器的图片
+ ///
+ public string Sprite = "res://resource/sprite/gun/gun1.png";
+ ///
+ /// 是否连续发射, 如果为false, 则每次发射都需要扣动扳机
+ ///
+ public bool ContinuousShoot = true;
+ ///
+ /// 弹夹容量
+ ///
+ public int AmmoCapacity = 30;
+ ///
+ /// 弹药容量上限
+ ///
+ public int MaxAmmoCapacity = 90;
+ ///
+ /// 装弹时间, 单位: 秒
+ ///
+ public float ReloadTime = 1.5f;
+ ///
+ /// 每粒子弹是否是单独装填, 如果是, 那么每上一发子弹的时间就是 ReloadTime, 可以做霰弹武器装填效果
+ ///
+ public bool AloneReload = false;
+ ///
+ /// 单独装填时每次装填子弹数量, 必须要将 'AloneReload' 属性设置为 true
+ ///
+ public int AloneReloadCount = 1;
+ ///
+ /// 单独装填的子弹时可以立即射击, 必须要将 'AloneReload' 属性设置为 true
+ ///
+ public bool AloneReloadCanShoot = false;
+ ///
+ /// 是否为松发开火, 也就是松开扳机才开火, 若要启用该属性, 必须将 'ContinuousShoot' 设置为 false
+ ///
+ public bool LooseShoot = false;
+ ///
+ /// 连续发射最小次数, 仅当 ContinuousShoot 为 false 时生效
+ ///
+ public int MinContinuousCount = 3;
+ ///
+ /// 连续发射最大次数, 仅当 ContinuousShoot 为 false 时生效
+ ///
+ public int MaxContinuousCount = 3;
+ ///
+ /// 按下一次扳机后需要多长时间才能再次按下
+ ///
+ public float TriggerInterval = 0;
+ ///
+ /// 初始射速, 初始每秒分钟能发射多少发子弹
+ ///
+ public float StartFiringSpeed = 300;
+ ///
+ /// 最终射速, 最终每秒分钟能发射多少发子弹
+ ///
+ public float FinalFiringSpeed = 300;
+ ///
+ /// 按下扳机并开火后射速增加速率
+ ///
+ public float FiringSpeedAddSpeed = 2;
+ ///
+ /// 松开扳机后射速消散速率
+ ///
+ public float FiringSpeedBackSpeed = 10;
+ ///
+ /// 单次开火发射子弹最小数量
+ ///
+ public int MinFireBulletCount = 1;
+ ///
+ /// 单次开火发射子弹最大数量
+ ///
+ public int MaxFireBulletCount = 1;
+ ///
+ /// 开火前延时
+ ///
+ public float DelayedTime = 0f;
+ ///
+ /// 初始散射半径
+ ///
+ public float StartScatteringRange = 0;
+ ///
+ /// 最终散射半径
+ ///
+ public float FinalScatteringRange = 20;
+ ///
+ /// 每次发射后散射增加值
+ ///
+ public float ScatteringRangeAddValue = 2;
+ ///
+ /// 松开扳机后散射销退速率
+ ///
+ public float ScatteringRangeBackSpeed = 10;
+ ///
+ /// 子弹飞行最大距离
+ ///
+ public float MaxDistance = 600;
+ ///
+ /// 子弹飞行最小距离
+ ///
+ public float MinDistance = 800;
+ ///
+ /// 武器精灵的旋转中心坐标
+ ///
+ public Vector2 CenterPosition = new Vector2(0, 0);
+ ///
+ /// 开火位置
+ ///
+ public Vector2 FirePosition = new Vector2(11, 0);
+ ///
+ /// 握把位置
+ ///
+ public Vector2 HoldPosition = new Vector2(4, -3);
+ ///
+ /// 重量
+ ///
+ public float Weight = 11;
+ ///
+ /// 最大后坐力 (仅用于开火后武器身抖动)
+ ///
+ public float MaxBacklash = 4;
+ ///
+ /// 最小后坐力 (仅用于开火后武器身抖动)
+ ///
+ public float MinBacklash = 2;
+ ///
+ /// 开火后武器口上抬角度
+ ///
+ public float UpliftAngle = 30;
+ ///
+ /// 开火后武器口角度恢复速度倍数
+ ///
+ public float UpliftAngleRestore = 1;
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/WeaponManager.cs b/DungeonShooting_Godot/src/game/props/weapon/WeaponManager.cs
new file mode 100644
index 0000000..d5e8bf1
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/WeaponManager.cs
@@ -0,0 +1,118 @@
+using System.Reflection;
+using System;
+using System.Collections.Generic;
+using Godot;
+
+///
+/// 武器管理类, 负责武器注册和创建
+///
+public static class WeaponManager
+{
+ private static Dictionary> registerData = new Dictionary>();
+
+ ///
+ /// 从一个指定的程序集中扫描并注册武器对象
+ ///
+ /// 数据集
+ public static void RegisterWeaponFromAssembly(Assembly assembly)
+ {
+ var types = assembly.GetTypes();
+ foreach (var type in types)
+ {
+ //注册类
+ Attribute[] attribute = Attribute.GetCustomAttributes(type, typeof(RegisterWeapon), false);
+ if (attribute != null && attribute.Length > 0)
+ {
+ if (!typeof(Weapon).IsAssignableFrom(type))
+ {
+ throw new Exception($"注册武器类'{type.FullName}'没有继承类'Weapon'!");
+ }
+ var atts = (RegisterWeapon[])attribute;
+ foreach (var att in atts)
+ {
+ //注册类
+ if (att.AttributeType == null) //没有指定属性类型
+ {
+ RegisterWeapon(att.Id, () =>
+ {
+ return Activator.CreateInstance(type, att.Id, new WeaponAttribute()) as Weapon;
+ });
+ }
+ else
+ {
+ if (!typeof(WeaponAttribute).IsAssignableFrom(att.AttributeType))
+ {
+ throw new Exception($"注册武器类'{type.FullName}'标注的特性中参数'AttributeType'类型没有继承'WeaponAttribute'!");
+ }
+ RegisterWeapon(att.Id, () =>
+ {
+ return Activator.CreateInstance(type, att.Id, Activator.CreateInstance(att.AttributeType)) as Weapon;
+ });
+ }
+ }
+ }
+
+ //注册函数
+ MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
+ foreach (var method in methods)
+ {
+ Attribute mAttribute;
+ //
+ if ((mAttribute = Attribute.GetCustomAttribute(method, typeof(RegisterWeaponFunction), false)) != null)
+ {
+ if (!typeof(Weapon).IsAssignableFrom(method.ReturnType)) //返回值类型不是 Weapon
+ {
+ throw new Exception($"注册武器函数'{method.DeclaringType.FullName}.{method.Name}'返回值类型不为'Weapon'!");
+ }
+ var args = method.GetParameters();
+ if (args == null || args.Length != 1 || args[0].ParameterType != typeof(string)) //参数类型不正确
+ {
+ throw new Exception($"注册武器函数'{method.DeclaringType.FullName}.{method.Name}'参数不满足(string id)类型");
+ }
+ var att = (RegisterWeaponFunction)mAttribute;
+ //注册函数
+ RegisterWeapon(att.Id, () =>
+ {
+ return method.Invoke(null, new object[] { att.Id }) as Weapon;
+ });
+ }
+ }
+ }
+ }
+
+ ///
+ /// 注册当个武器对象
+ ///
+ /// 武器唯一id, 该id不能重复
+ /// 获取武器时的回调函数, 函数返回武器对象
+ public static void RegisterWeapon(string id, Func callBack)
+ {
+ if (registerData.ContainsKey(id))
+ {
+ throw new Exception($"武器id: '{id} 已经被注册!'");
+ }
+ registerData.Add(id, callBack);
+ }
+
+ ///
+ /// 根据武器唯一id获取
+ ///
+ /// 武器id
+ public static Weapon GetGun(string id)
+ {
+ if (registerData.TryGetValue(id, out var callback))
+ {
+ return callback();
+ }
+ throw new Exception($"当前武器'{id}未被注册'");
+ }
+
+ ///
+ /// 根据武器唯一id获取
+ ///
+ /// 武器id
+ public static T GetGun(string id) where T : Weapon
+ {
+ return (T)GetGun(id);
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/props/weapon/WeaponWeightType.cs b/DungeonShooting_Godot/src/game/props/weapon/WeaponWeightType.cs
new file mode 100644
index 0000000..5528683
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/WeaponWeightType.cs
@@ -0,0 +1,19 @@
+
+///
+/// 根据重量划分的武器类型
+///
+public enum WeaponWeightType
+{
+ ///
+ /// 副武器
+ ///
+ DeputyWeapon = 1,
+ ///
+ /// 主武器
+ ///
+ MainWeapon = 2,
+ ///
+ /// 重型武器
+ ///
+ HeavyWeapon = 3
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/bullet/Bullet.cs b/DungeonShooting_Godot/src/game/props/weapon/bullet/Bullet.cs
new file mode 100644
index 0000000..4c19b4b
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/bullet/Bullet.cs
@@ -0,0 +1,27 @@
+using Godot;
+
+///
+/// 子弹接口
+///
+public interface IBullet
+{
+ ///
+ /// 攻击目标阵营
+ ///
+ CampEnum TargetCamp { get; }
+ ///
+ /// 发射该子弹的武器
+ ///
+ Weapon Gun { get; }
+ ///
+ /// 发射该子弹的物体对象
+ ///
+ Node2D Master { get; }
+ ///
+ /// 初始化基础数据
+ ///
+ /// 攻击的目标阵营
+ /// 发射该子弹的枪对象
+ /// 发射该子弹的角色
+ void Init(CampEnum target, Weapon gun, Node2D master);
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/bullet/HighSpeedBullet.cs b/DungeonShooting_Godot/src/game/props/weapon/bullet/HighSpeedBullet.cs
new file mode 100644
index 0000000..53ef711
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/bullet/HighSpeedBullet.cs
@@ -0,0 +1,72 @@
+using Godot;
+
+///
+/// 高速子弹
+///
+public class HighSpeedBullet : Node2D, IBullet
+{
+ public CampEnum TargetCamp { get; private set; }
+
+ public Weapon Gun { get; private set; }
+
+ public Node2D Master { get; private set; }
+
+ ///
+ /// 碰撞物体后产生的火花
+ ///
+ [Export] public PackedScene Hit;
+
+ //射线检测节点
+ private RayCast2D RayCast2D;
+ private Line2D Line;
+ private float ca = 1;
+
+ public void Init(CampEnum target, Weapon gun, Node2D master)
+ {
+ TargetCamp = target;
+ Gun = gun;
+ Master = master;
+
+ //飞行距离
+ var distance = MathUtils.RandRange(gun.Attribute.MinDistance, gun.Attribute.MaxDistance);
+
+ //初始化子弹数据
+ RayCast2D = GetNode("RayCast2D");
+ Line = GetNode("Line");
+ Modulate = Colors.White;
+
+ // 目标点
+ Vector2 targetPos = new Vector2(distance, 0);
+ RayCast2D.CastTo = targetPos;
+ RayCast2D.ForceRaycastUpdate();
+ if (RayCast2D.IsColliding())
+ {
+ //碰到物体
+ Vector2 collPosition = RayCast2D.GetCollisionPoint();
+ Node2D hit = Hit.Instance();
+ hit.RotationDegrees = MathUtils.RandRangeInt(0, 360);
+ hit.GlobalPosition = collPosition;
+ GetTree().CurrentScene.AddChild(hit);
+ //划线的点坐标
+ Line.SetPointPosition(1, new Vector2(Line.GlobalPosition.DistanceTo(collPosition), 0));
+ }
+ else
+ {
+ //划线的点坐标
+ Line.SetPointPosition(1, targetPos);
+ }
+ RayCast2D.Enabled = false;
+ }
+
+ public override void _Process(float delta)
+ {
+ ca -= 12 * delta;
+ if (ca <= 0) {
+ QueueFree();
+ return;
+ }
+ Color c = Modulate;
+ c.a = ca;
+ Modulate = c;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/bullet/OrdinaryBullets.cs b/DungeonShooting_Godot/src/game/props/weapon/bullet/OrdinaryBullets.cs
new file mode 100644
index 0000000..4458e5f
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/bullet/OrdinaryBullets.cs
@@ -0,0 +1,97 @@
+using System;
+using Godot;
+
+///
+/// 普通的子弹
+///
+public class OrdinaryBullets : Node2D, IBullet
+{
+ public CampEnum TargetCamp { get; private set; }
+
+ public Weapon Gun { get; private set; }
+
+ public Node2D Master { get; private set; }
+
+ ///
+ /// 碰撞物体后产生的火花
+ ///
+ [Export] public PackedScene Hit;
+
+ // 起始点坐标
+ private Vector2 StartPosition;
+ // 最大飞行距离
+ private float MaxDistance;
+ // 子弹飞行速度
+ private float FlySpeed = 1500;
+ //当前子弹已经飞行的距离
+ private float CurrFlyDistance = 0;
+ //射线碰撞节点
+ private RayCast2D RayCast;
+ //子弹的精灵
+ private Sprite BulletSprite;
+ //绘制阴影的精灵
+ private Sprite ShadowSprite;
+
+ private int frame = 0;
+
+ public void Init(CampEnum target, Weapon gun, Node2D master)
+ {
+ TargetCamp = target;
+ Gun = gun;
+ Master = master;
+
+ MaxDistance = MathUtils.RandRange(gun.Attribute.MinDistance, gun.Attribute.MaxDistance);
+ StartPosition = GlobalPosition;
+ BulletSprite = GetNode("Bullet");
+ BulletSprite.Visible = false;
+ RayCast = GetNode("RayCast2D");
+
+ //创建阴影
+ ShadowSprite = new Sprite();
+ ShadowSprite.Visible = false;
+ ShadowSprite.ZIndex = -1;
+ ShadowSprite.Texture = BulletSprite.Texture;
+ ShadowSprite.Offset = BulletSprite.Offset;
+ ShadowSprite.Material = ResourceManager.ShadowMaterial;
+ AddChild(ShadowSprite);
+ }
+
+ public override void _Ready()
+ {
+ //生成时播放音效
+ SoundManager.PlaySoundEffect("ordinaryBullet.ogg", this, 6f);
+ }
+
+ public override void _PhysicsProcess(float delta)
+ {
+ if (frame++ == 0)
+ {
+ BulletSprite.Visible = true;
+ ShadowSprite.Visible = true;
+ }
+ //碰到墙壁
+ if (RayCast.IsColliding())
+ {
+ //var target = RayCast.GetCollider();
+ var pos = RayCast.GetCollisionPoint();
+ //播放受击动画
+ Node2D hit = Hit.Instance();
+ hit.RotationDegrees = MathUtils.RandRangeInt(0, 360);
+ hit.GlobalPosition = pos;
+ GetTree().CurrentScene.AddChild(hit);
+ QueueFree();
+ }
+ else //没有碰到, 继续移动
+ {
+ ShadowSprite.GlobalPosition = GlobalPosition + new Vector2(0, 5);
+ Position += new Vector2(FlySpeed * delta, 0).Rotated(Rotation);
+
+ CurrFlyDistance += FlySpeed * delta;
+ if (CurrFlyDistance >= MaxDistance)
+ {
+ QueueFree();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/gun/Gun.cs b/DungeonShooting_Godot/src/game/props/weapon/gun/Gun.cs
new file mode 100644
index 0000000..ca9ce7e
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/gun/Gun.cs
@@ -0,0 +1,161 @@
+using Godot;
+
+[RegisterWeapon("1001", typeof(Gun.RifleAttribute))]
+[RegisterWeapon("1003", typeof(Gun.PistolAttribute))]
+///
+/// 普通的枪
+///
+public class Gun : Weapon
+{
+ //步枪属性数据
+ private class RifleAttribute : WeaponAttribute
+ {
+ public RifleAttribute()
+ {
+ Name = "步枪";
+ Sprite = "res://resource/sprite/gun/gun4.png";
+ Weight = 40;
+ CenterPosition = new Vector2(0.4f, -2.6f);
+ StartFiringSpeed = 480;
+ StartScatteringRange = 30;
+ FinalScatteringRange = 90;
+ ScatteringRangeAddValue = 2f;
+ ScatteringRangeBackSpeed = 40;
+ //连发
+ ContinuousShoot = true;
+ //扳机检测间隔
+ TriggerInterval = 0f;
+ //连发数量
+ MinContinuousCount = 3;
+ MaxContinuousCount = 3;
+ //开火前延时
+ DelayedTime = 0f;
+ //攻击距离
+ MinDistance = 500;
+ MaxDistance = 600;
+ //发射子弹数量
+ MinFireBulletCount = 1;
+ MaxFireBulletCount = 1;
+ //抬起角度
+ UpliftAngle = 10;
+ //枪身长度
+ FirePosition = new Vector2(16, 1.5f);
+ }
+ }
+
+ //手枪属性数据
+ private class PistolAttribute : WeaponAttribute
+ {
+ public PistolAttribute()
+ {
+ Name = "手枪";
+ Sprite = "res://resource/sprite/gun/gun3.png";
+ Weight = 20;
+ CenterPosition = new Vector2(0.4f, -2.6f);
+ WeightType = WeaponWeightType.DeputyWeapon;
+ StartFiringSpeed = 300;
+ StartScatteringRange = 5;
+ FinalScatteringRange = 60;
+ ScatteringRangeAddValue = 8f;
+ ScatteringRangeBackSpeed = 40;
+ //连发
+ ContinuousShoot = false;
+ AmmoCapacity = 12;
+ MaxAmmoCapacity = 72;
+ //扳机检测间隔
+ TriggerInterval = 0.1f;
+ //连发数量
+ MinContinuousCount = 1;
+ MaxContinuousCount = 1;
+ //开火前延时
+ DelayedTime = 0f;
+ //攻击距离
+ MinDistance = 500;
+ MaxDistance = 600;
+ //发射子弹数量
+ MinFireBulletCount = 1;
+ MaxFireBulletCount = 1;
+ //抬起角度
+ UpliftAngle = 30;
+ //枪身长度
+ FirePosition = new Vector2(10, 1.5f);
+ }
+ }
+
+ ///
+ /// 子弹预制体
+ ///
+ public PackedScene BulletPack;
+ ///
+ /// 弹壳预制体
+ ///
+ public PackedScene ShellPack;
+
+ public Gun(string id, WeaponAttribute attribute): base(id, attribute)
+ {
+ BulletPack = ResourceManager.Load("res://prefab/weapon/bullet/OrdinaryBullets.tscn");
+ ShellPack = ResourceManager.Load("res://prefab/weapon/shell/ShellCase.tscn");
+ }
+
+ protected override void OnFire()
+ {
+ //创建一个弹壳
+ var startPos = GlobalPosition + new Vector2(0, 5);
+ var startHeight = 6;
+ var direction = GlobalRotationDegrees + MathUtils.RandRangeInt(-30, 30) + 180;
+ var xf = MathUtils.RandRangeInt(20, 60);
+ var yf = MathUtils.RandRangeInt(60, 120);
+ var rotate = MathUtils.RandRangeInt(-720, 720);
+ var sprite = ShellPack.Instance();
+ sprite.StartThrow(new Vector2(5, 10), startPos, startHeight, direction, xf, yf, rotate, sprite);
+ //创建抖动
+ MainCamera.Main.ProssesDirectionalShake(Vector2.Right.Rotated(GlobalRotation) * 1.5f);
+ }
+
+ protected override void OnShoot()
+ {
+ //创建子弹
+ CreateBullet(BulletPack, FirePoint.GlobalPosition, (FirePoint.GlobalPosition - OriginPoint.GlobalPosition).Angle());
+ }
+
+ protected override void OnReload()
+ {
+
+ }
+
+ protected override void OnReloadFinish()
+ {
+
+ }
+
+ protected override void OnDownTrigger()
+ {
+
+ }
+
+ protected override void OnUpTrigger()
+ {
+
+ }
+
+ protected override void OnPickUp(Role master)
+ {
+
+ }
+
+ protected override void OnThrowOut()
+ {
+
+ }
+
+ protected override void OnActive()
+ {
+
+ }
+
+ protected override void OnConceal()
+ {
+
+ }
+
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/props/weapon/gun/Shotgun.cs b/DungeonShooting_Godot/src/game/props/weapon/gun/Shotgun.cs
new file mode 100644
index 0000000..66cff82
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/gun/Shotgun.cs
@@ -0,0 +1,125 @@
+using Godot;
+
+[RegisterWeapon("1002", typeof(Shotgun.ShotgunAttribute))]
+public class Shotgun : Weapon
+{
+
+ private class ShotgunAttribute : WeaponAttribute
+ {
+ public ShotgunAttribute()
+ {
+ Name = "霰弹枪";
+ Sprite = "res://resource/sprite/gun/gun2.png";
+ Weight = 40;
+ CenterPosition = new Vector2(0.4f, -2.6f);
+ StartFiringSpeed = 120;
+ StartScatteringRange = 30;
+ FinalScatteringRange = 90;
+ ScatteringRangeAddValue = 50f;
+ ScatteringRangeBackSpeed = 50;
+ //连发
+ ContinuousShoot = false;
+ AmmoCapacity = 7;
+ MaxAmmoCapacity = 42;
+ AloneReload = true;
+ AloneReloadCanShoot = true;
+ ReloadTime = 0.3f;
+ //连发数量
+ MinContinuousCount = 1;
+ MaxContinuousCount = 1;
+ //开火前延时
+ DelayedTime = 0f;
+ //攻击距离
+ MinDistance = 500;
+ MaxDistance = 600;
+ //发射子弹数量
+ MinFireBulletCount = 1;
+ MaxFireBulletCount = 1;
+ //抬起角度
+ UpliftAngle = 15;
+ MaxBacklash = 6;
+ MinBacklash = 5;
+ //枪身长度
+ FirePosition = new Vector2(16, 1.5f);
+ }
+ }
+
+ ///
+ /// 子弹预制体
+ ///
+ public PackedScene BulletPack;
+ ///
+ /// 弹壳预制体
+ ///
+ public PackedScene ShellPack;
+
+ public Shotgun(string id, WeaponAttribute attribute) : base(id, attribute)
+ {
+ BulletPack = ResourceManager.Load("res://prefab/weapon/bullet/OrdinaryBullets.tscn");
+ ShellPack = ResourceManager.Load("res://prefab/weapon/shell/ShellCase.tscn");
+ }
+
+ protected override void OnFire()
+ {
+ //创建一个弹壳
+ var startPos = GlobalPosition + new Vector2(0, 5);
+ var startHeight = 6;
+ var direction = GlobalRotationDegrees + MathUtils.RandRangeInt(-30, 30) + 180;
+ var xf = MathUtils.RandRangeInt(20, 60);
+ var yf = MathUtils.RandRangeInt(60, 120);
+ var rotate = MathUtils.RandRangeInt(-720, 720);
+ var sprite = ShellPack.Instance();
+ sprite.StartThrow(new Vector2(5, 10), startPos, startHeight, direction, xf, yf, rotate, sprite);
+ //创建抖动
+ MainCamera.Main.ProssesDirectionalShake(Vector2.Right.Rotated(GlobalRotation) * 1.5f);
+ }
+
+ protected override void OnShoot()
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ //创建子弹
+ CreateBullet(BulletPack, FirePoint.GlobalPosition, (FirePoint.GlobalPosition - OriginPoint.GlobalPosition).Angle() + MathUtils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi));
+ }
+ }
+
+ protected override void OnReload()
+ {
+
+ }
+
+ protected override void OnReloadFinish()
+ {
+
+ }
+
+ protected override void OnDownTrigger()
+ {
+
+ }
+
+ protected override void OnUpTrigger()
+ {
+
+ }
+
+ protected override void OnPickUp(Role master)
+ {
+
+ }
+
+ protected override void OnThrowOut()
+ {
+
+ }
+
+ protected override void OnActive()
+ {
+
+ }
+
+ protected override void OnConceal()
+ {
+
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/props/weapon/shell/ThrowShell.cs b/DungeonShooting_Godot/src/game/props/weapon/shell/ThrowShell.cs
new file mode 100644
index 0000000..914ee5d
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/props/weapon/shell/ThrowShell.cs
@@ -0,0 +1,42 @@
+using Godot;
+
+///
+/// 弹壳
+///
+public class ThrowShell : ThrowNode
+{
+
+ public override void _Ready()
+ {
+ base._Ready();
+ ZIndex = 2;
+ }
+
+ protected override void OnOver()
+ {
+ //如果落地高度不够低, 再抛一次
+ if (StartYSpeed > 1)
+ {
+ StartThrow(Size, GlobalPosition, 0, Direction, XSpeed * 0.8f, StartYSpeed * 0.5f, RotateSpeed * 0.5f, null);
+ }
+ else
+ {
+ base.OnOver();
+ //等待被销毁
+ AwaitDestroy();
+ }
+ }
+
+ private async void AwaitDestroy()
+ {
+ CollisionShape.Disabled = true;
+ //60秒后销毁
+ await ToSignal(GetTree().CreateTimer(60), "timeout");
+ QueueFree();
+ }
+
+ protected override void OnMaxHeight(float height)
+ {
+ ZIndex = 0;
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/AnimatorNames.cs b/DungeonShooting_Godot/src/game/role/AnimatorNames.cs
new file mode 100644
index 0000000..5645333
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/AnimatorNames.cs
@@ -0,0 +1,7 @@
+
+public static class AnimatorNames
+{
+ public static readonly string Idle = "idle";
+ public static readonly string Run = "run";
+ public static readonly string ReverseRun = "reverseRun";
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/CampEnum.cs b/DungeonShooting_Godot/src/game/role/CampEnum.cs
new file mode 100644
index 0000000..afe6f3b
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/CampEnum.cs
@@ -0,0 +1,8 @@
+
+public enum CampEnum
+{
+ // 友军
+ Friend,
+ // 敌人
+ Enemy
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/Player.cs b/DungeonShooting_Godot/src/game/role/Player.cs
new file mode 100644
index 0000000..efe2d5c
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/Player.cs
@@ -0,0 +1,235 @@
+using Godot;
+
+public class Player : Role
+{
+ ///
+ /// 移动加速度
+ ///
+ public float Acceleration = 1500f;
+
+ ///
+ /// 移动摩擦力
+ ///
+ public float Friction = 800f;
+ ///
+ /// 移动速度
+ ///
+ public Vector2 Velocity = Vector2.Zero;
+
+ ///
+ /// 当前护盾值
+ ///
+ public int Shield
+ {
+ get => _shield;
+ protected set
+ {
+ int temp = _shield;
+ _shield = value;
+ if (temp != _shield) OnChangeShield(_shield);
+ }
+ }
+ private int _shield = 0;
+
+ ///
+ /// 最大护盾值
+ ///
+ public int MaxShield
+ {
+ get => _maxShield;
+ protected set
+ {
+ int temp = _maxShield;
+ _maxShield = value;
+ if (temp != _maxShield) OnChangeMaxShield(_maxShield);
+ }
+ }
+ private int _maxShield = 0;
+
+
+ [Export] public PackedScene GunPrefab;
+
+ public override void _EnterTree()
+ {
+ base._EnterTree();
+ RoomManager.Current.Player = this;
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+ Holster.SlotList[2].Enable = true;
+ Holster.SlotList[3].Enable = true;
+ RefreshGunTexture();
+
+ MaxHp = 50;
+ Hp = 40;
+ MaxShield = 30;
+ Shield = 10;
+ }
+
+ public override void _Process(float delta)
+ {
+ base._Process(delta);
+
+ Vector2 mousePos = GetGlobalMousePosition();
+ //枪口跟随鼠标
+ MountPoint.LookAt(mousePos);
+ //脸的朝向
+ if (mousePos.x > GlobalPosition.x && Face == FaceDirection.Left)
+ {
+ Face = FaceDirection.Right;
+ }
+ else if (mousePos.x < GlobalPosition.x && Face == FaceDirection.Right)
+ {
+ Face = FaceDirection.Left;
+ }
+
+ if (Input.IsActionJustPressed("exchange")) //切换武器
+ {
+ TriggerExchangeNext();
+ RefreshGunTexture();
+ }
+ else if (Input.IsActionJustPressed("throw")) //扔掉武器
+ {
+ TriggerThrowWeapon();
+ RefreshGunTexture();
+ }
+ else if (Input.IsActionJustPressed("interactive")) //互动物体
+ {
+ var item = TriggerTnteractive();
+ if (item is Weapon)
+ {
+ RefreshGunTexture();
+ }
+ }
+ else if (Input.IsActionJustPressed("reload")) //换弹
+ {
+ TriggerReload();
+ }
+ if (Input.IsActionPressed("fire")) //开火
+ {
+ TriggerAttack();
+ }
+ //刷新显示的弹药剩余量
+ RefreshGunAmmunition();
+
+ if (Holster.ActiveWeapon != null && Holster.ActiveWeapon.Reloading)
+ {
+ RoomUI.Current.ReloadBar.ShowBar(GlobalPosition, 1 - Holster.ActiveWeapon.ReloadProgress);
+ }
+ else
+ {
+ RoomUI.Current.ReloadBar.HideBar();
+ }
+ }
+
+ public override void _PhysicsProcess(float delta)
+ {
+ base._PhysicsProcess(delta);
+ Move(delta);
+ //播放动画
+ PlayAnim();
+ }
+
+ protected override void OnChangeHp(int hp)
+ {
+ RoomUI.Current.SetHp(hp);
+ }
+
+ protected override void OnChangeMaxHp(int maxHp)
+ {
+ RoomUI.Current.SetMaxHp(maxHp);
+ }
+
+ protected override void ChangeInteractiveItem(CheckInteractiveResult result)
+ {
+ if (result == null)
+ {
+ //隐藏互动提示
+ RoomUI.Current.InteractiveTipBar.HideBar();
+ }
+ else
+ {
+ if (InteractiveItem is Weapon gun)
+ {
+ //显示互动提示
+ RoomUI.Current.InteractiveTipBar.ShowBar(result.Target.GetItemPosition(), result.ShowIcon, result.Message);
+ }
+ }
+ }
+
+ protected void OnChangeShield(int shield)
+ {
+ RoomUI.Current.SetShield(shield);
+ }
+
+ protected void OnChangeMaxShield(int maxShield)
+ {
+ RoomUI.Current.SetMaxShield(maxShield);
+ }
+
+ ///
+ /// 刷新 ui 上手持的物体
+ ///
+ private void RefreshGunTexture()
+ {
+ var gun = Holster.ActiveWeapon;
+ if (gun != null)
+ {
+ RoomUI.Current.SetGunTexture(gun.WeaponSprite.Texture);
+ }
+ else
+ {
+ RoomUI.Current.SetGunTexture(null);
+ }
+ }
+
+ ///
+ /// 刷新 ui 上显示的弹药量
+ ///
+ private void RefreshGunAmmunition()
+ {
+ var gun = Holster.ActiveWeapon;
+ if (gun != null)
+ {
+ RoomUI.Current.SetAmmunition(gun.CurrAmmo, gun.ResidueAmmo);
+ }
+ }
+
+ private void Move(float delta)
+ {
+ //角色移动
+ // 得到输入的 vector2 getvector方法返回值已经归一化过了noemalized
+ Vector2 dir = Input.GetVector("move_left", "move_right", "move_up", "move_down");
+ // 移动. 如果移动的数值接近0(是用 摇杆可能出现 方向 可能会出现浮点),就fricition的值 插值 到 0
+ // 如果 有输入 就以当前速度,用acceleration 插值到 对应方向 * 最大速度
+ if (Mathf.IsZeroApprox(dir.x)) Velocity.x = Mathf.MoveToward(Velocity.x, 0, Friction * delta);
+ else Velocity.x = Mathf.MoveToward(Velocity.x, dir.x * MoveSpeed, Acceleration * delta);
+
+ if (Mathf.IsZeroApprox(dir.y)) Velocity.y = Mathf.MoveToward(Velocity.y, 0, Friction * delta);
+ else Velocity.y = Mathf.MoveToward(Velocity.y, dir.y * MoveSpeed, Acceleration * delta);
+
+ Velocity = MoveAndSlide(Velocity);
+ }
+
+ // 播放动画
+ private void PlayAnim()
+ {
+ if (Velocity != Vector2.Zero)
+ {
+ if ((Face == FaceDirection.Right && Velocity.x >= 0) || Face == FaceDirection.Left && Velocity.x <= 0) //向前走
+ {
+ AnimatedSprite.Animation = AnimatorNames.Run;
+ }
+ else if ((Face == FaceDirection.Right && Velocity.x < 0) || Face == FaceDirection.Left && Velocity.x > 0) //向后走
+ {
+ AnimatedSprite.Animation = AnimatorNames.ReverseRun;
+ }
+ }
+ else
+ {
+ AnimatedSprite.Animation = AnimatorNames.Idle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs
new file mode 100644
index 0000000..362981b
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/Role.cs
@@ -0,0 +1,336 @@
+using System.Collections.Generic;
+using Godot;
+
+///
+/// 脸的朝向
+///
+public enum FaceDirection
+{
+ Left,
+ Right,
+}
+
+///
+/// 角色基类
+///
+public abstract class Role : KinematicBody2D
+{
+ ///
+ /// 重写的纹理
+ ///
+ [Export] public Texture Texture;
+
+ ///
+ /// 移动速度
+ ///
+ [Export] public float MoveSpeed = 150f;
+
+ ///
+ /// 所属阵营
+ ///
+ [Export] public CampEnum Camp;
+
+ ///
+ /// 携带的道具包裹
+ ///
+ public List