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 PropsPack { get; } = new List(); + + /// + /// 角色携带的枪套 + /// + public Holster Holster { get; private set; } + + /// + /// 动画播放器 + /// + public AnimatedSprite AnimatedSprite { get; private set; } + /// + /// 武器挂载点 + /// + public Position2D MountPoint { get; private set; } + /// + /// 背后武器的挂载点 + /// + public Position2D BackMountPoint { get; private set; } + + /// + /// 脸的朝向 + /// + public FaceDirection Face { get => _face; set => SetFace(value); } + private FaceDirection _face; + + /// + /// 血量 + /// + public int Hp + { + get => _hp; + protected set + { + int temp = _hp; + _hp = value; + if (temp != _hp) OnChangeHp(_hp); + } + } + private int _hp = 0; + + /// + /// 最大血量 + /// + public int MaxHp + { + get => _maxHp; + protected set + { + int temp = _maxHp; + _maxHp = value; + if (temp != _maxHp) OnChangeMaxHp(_maxHp); + } + } + private int _maxHp = 0; + + private Vector2 StartScele; + //所有角色碰撞的道具 + private readonly List InteractiveItemList = new List(); + + private CheckInteractiveResult TempResultData; + + /// + /// 可以互动的道具 + /// + protected IProp InteractiveItem { get; private set; } + + /// + /// 当血量改变时调用 + /// + protected abstract void OnChangeHp(int hp); + /// + /// 当最大血量改变时调用 + /// + protected abstract void OnChangeMaxHp(int maxHp); + + /// + /// 当可互动的物体改变时调用, result 参数为 null 表示变为不可互动 + /// + /// 检测是否可互动时的返回值 + protected abstract void ChangeInteractiveItem(CheckInteractiveResult result); + + public override void _Ready() + { + StartScele = Scale; + AnimatedSprite = GetNode("AnimatedSprite"); + MountPoint = GetNode("MountPoint"); + BackMountPoint = GetNode("BackMountPoint"); + // 更改纹理 + ChangeFrameTexture(AnimatorNames.Idle, AnimatedSprite, Texture); + ChangeFrameTexture(AnimatorNames.Run, AnimatedSprite, Texture); + ChangeFrameTexture(AnimatorNames.ReverseRun, AnimatedSprite, Texture); + + Holster = new Holster(this); + Face = FaceDirection.Right; + } + + public override void _Process(float delta) + { + //检查可互动的道具 + bool findFlag = false; + for (int i = 0; i < InteractiveItemList.Count; i++) + { + var item = InteractiveItemList[i]; + if (item == null) + { + InteractiveItemList.RemoveAt(i--); + } + else + { + //找到可互动的道具了 + if (!findFlag) + { + var result = item.CheckInteractive(this); + if (result.CanInteractive) //可以互动 + { + findFlag = true; + if (InteractiveItem != item) //更改互动物体 + { + InteractiveItem = item; + ChangeInteractiveItem(result); + } + else if (result.ShowIcon != TempResultData.ShowIcon) //切换状态 + { + ChangeInteractiveItem(result); + } + } + TempResultData = result; + } + } + } + //没有可互动的道具 + if (!findFlag && InteractiveItem != null) + { + InteractiveItem = null; + ChangeInteractiveItem(null); + } + } + + + /// + /// 拾起一个武器, 并且切换到这个武器 + /// + /// 武器对象 + public void PickUpWeapon(Weapon weapon) + { + if (Holster.PickupWeapon(weapon) != -1) + { + //从可互动队列中移除 + InteractiveItemList.Remove(weapon); + } + } + + /// + /// 切换到下一个武器 + /// + public void TriggerExchangeNext() + { + Holster.ExchangeNext(); + } + + /// + /// 切换到上一个武器 + /// + public void ExchangePrev() + { + Holster.ExchangePrev(); + } + + /// + /// 扔掉当前使用的武器, 切换到上一个武器 + /// + public void TriggerThrowWeapon() + { + var weapon = Holster.RmoveWeapon(Holster.ActiveIndex); + //播放抛出效果 + if (weapon != null) + { + weapon.StartThrowWeapon(this); + } + } + + /// + /// 返回是否存在可互动的物体 + /// + public bool HasTnteractive() + { + return InteractiveItem != null; + } + + /// + /// 触发与碰撞的物体互动, 并返回与其互动的物体 + /// + public IProp TriggerTnteractive() + { + if (HasTnteractive()) + { + var item = InteractiveItem; + item.Interactive(this); + return item; + } + return null; + } + + /// + /// 触发换弹 + /// + public void TriggerReload() + { + if (Holster.ActiveWeapon != null) + { + Holster.ActiveWeapon._Reload(); + } + } + + /// + /// 触发攻击 + /// + public void TriggerAttack() + { + if (Holster.ActiveWeapon != null) + { + Holster.ActiveWeapon.Trigger(); + } + } + + /// + /// 设置脸的朝向 + /// + private void SetFace(FaceDirection face) + { + if (_face != face) + { + _face = face; + if (face == FaceDirection.Right) + { + RotationDegrees = 0; + Scale = StartScele; + } + else + { + RotationDegrees = 180; + Scale = new Vector2(StartScele.x, -StartScele.y); + } + } + } + + /// + /// 更改指定动画的纹理 + /// + private void ChangeFrameTexture(string anim, AnimatedSprite animatedSprite, Texture texture) + { + SpriteFrames spriteFrames = animatedSprite.Frames as SpriteFrames; + if (spriteFrames != null) + { + int count = spriteFrames.GetFrameCount(anim); + for (int i = 0; i < count; i++) + { + AtlasTexture temp = spriteFrames.GetFrame(anim, i) as AtlasTexture; + temp.Atlas = Texture; + } + } + } + + /// + /// 连接信号: InteractiveArea.area_entered + /// 与物体碰撞 + /// + private void _OnPropsEnter(Area2D other) + { + IProp prop = other.AsProp(); + if (prop != null) //道具类型 + { + if (!InteractiveItemList.Contains(prop)) + { + InteractiveItemList.Add(prop); + } + } + } + + /// + /// 连接信号: InteractiveArea.area_exited + /// 物体离开碰撞区域 + /// + private void _OnPropsExit(Area2D other) + { + IProp prop = other.AsProp(); + if (prop != null) //道具类型 + { + if (InteractiveItemList.Contains(prop)) + { + InteractiveItemList.Remove(prop); + } + if (InteractiveItem == prop) + { + InteractiveItem = null; + ChangeInteractiveItem(null); + } + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs new file mode 100644 index 0000000..b66bccf --- /dev/null +++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs @@ -0,0 +1,67 @@ +using System; +using Godot; + +/// +/// 房间管理器 +/// +public class RoomManager : Node2D +{ + /// + /// 鼠标指针 + /// + [Export] public PackedScene MouseCursor; + + public static RoomManager Current { get; private set; } + + public CanvasLayer UI; + public Cursor Cursor { get; private set; } + public Player Player { get; set; } + public Node2D ObjectRoot { get; private set; } + public YSort SortRoot { get; private set; } + + public override void _EnterTree() + { + Current = this; + Input.MouseMode = Input.MouseModeEnum.Hidden; + + UI = GetNode("UI"); + + // 初始化鼠标 + Cursor = MouseCursor.Instance(); + AddChild(Cursor); + + SortRoot = GetNode("ItemRoot"); + ObjectRoot = GetNode("ObjectRoot"); + + //初始化地图 + var node = GetNode("MapRoot").GetChild(0).GetNode("Config"); + Color color = (Color)node.GetMeta("ClearColor"); + VisualServer.SetDefaultClearColor(color); + } + + public override void _Ready() + { + //播放bgm + SoundManager.PlayeMusic("intro.ogg", this, -17f); + var gun1 = WeaponManager.GetGun("1001"); + gun1.Position = new Vector2(80, 80); + gun1.PutDown(gun1.WeaponSprite); + var gun2 = WeaponManager.GetGun("1002"); + gun2.Position = new Vector2(80, 120); + gun2.PutDown(gun2.WeaponSprite); + var gun3 = WeaponManager.GetGun("1003"); + gun3.Position = new Vector2(120, 80); + gun3.PutDown(gun3.WeaponSprite); + // var gun4 = WeaponManager.GetGun4(); + // gun4.Position = new Vector2(120, 120); + // gun4.PutDown(gun4.WeaponSprite); + // var gun5 = WeaponManager.GetGun5(); + // gun5.Position = new Vector2(160, 160); + // gun5.PutDown(gun5.WeaponSprite); + } + + public override void _Process(float delta) + { + + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/ui/InteractiveTipBar.cs b/DungeonShooting_Godot/src/game/ui/InteractiveTipBar.cs new file mode 100644 index 0000000..09f1f1b --- /dev/null +++ b/DungeonShooting_Godot/src/game/ui/InteractiveTipBar.cs @@ -0,0 +1,47 @@ +using Godot; + +/// +/// 互动提示文本 +/// +public class InteractiveTipBar : Node2D +{ + + private Label Message; + private Sprite Icon; + private Sprite Bg; + + private string currImage; + + public override void _Ready() + { + Message = GetNode