初次提交
This commit is contained in:
71
Script/Pawn/BagItemList.cs
Normal file
71
Script/Pawn/BagItemList.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace Cosmobox
|
||||
{
|
||||
public partial class BagItemList
|
||||
{
|
||||
public List<BagItem> Items = new();
|
||||
|
||||
// 查找物品
|
||||
public BagItem Find(string name)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item.ItemName == name)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 添加物品
|
||||
public bool AddItem(ItemResource resource, int amount = 1)
|
||||
{
|
||||
if (resource == null || amount <= 0)
|
||||
{
|
||||
GD.PrintErr("Invalid item or amount.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已有相同物品
|
||||
var existingItem = Find(resource.ItemName);
|
||||
if (existingItem != null && resource.Stackable)
|
||||
{
|
||||
existingItem.AddAmount(amount); // 堆叠物品
|
||||
}
|
||||
else
|
||||
{
|
||||
// 创建新物品条目
|
||||
var newItem = new BagItem
|
||||
{
|
||||
itemResource = resource,
|
||||
amount = amount
|
||||
};
|
||||
Items.Add(newItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 移除物品
|
||||
public bool RemoveItem(string name, int amount = 1)
|
||||
{
|
||||
var item = Find(name);
|
||||
if (item == null)
|
||||
{
|
||||
GD.Print($"Item '{name}' not found in bag.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amount >= item.amount)
|
||||
{
|
||||
Items.Remove(item); // 完全移除物品
|
||||
return true;
|
||||
}
|
||||
|
||||
item.RemoveAmount(amount); // 减少数量
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
1
Script/Pawn/BagItemList.cs.uid
Normal file
1
Script/Pawn/BagItemList.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b1fsx76po5ksu
|
225
Script/Pawn/CameraControl.cs
Normal file
225
Script/Pawn/CameraControl.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using Godot;
|
||||
using System;
|
||||
namespace Cosmobox
|
||||
{
|
||||
public partial class CameraControl : Camera2D
|
||||
{
|
||||
[Export] public Node2D Target; // 要跟随的目标(通常为玩家角色)
|
||||
[Export] public bool EnableSmoothing = true; // 启用平滑跟随
|
||||
[Export(PropertyHint.Range, "0.1,10,0.1")]
|
||||
public float SmoothingSpeed = 2.5f; // 平滑跟随速度(推荐值2.0-4.0)
|
||||
|
||||
[Export] public bool EnableBounds = false; // 启用边界限制
|
||||
[Export] public Rect2 CameraBounds; // 摄像机移动边界
|
||||
|
||||
[Export] public Vector2 PositionOffset = Vector2.Zero; // 目标位置偏移
|
||||
[Export] public bool ScaleWithZoom = true; // 边界是否随缩放调整
|
||||
|
||||
[Export] public float DeadZoneRadius = 0; // 摄像机不移动的半径(减少微小移动)
|
||||
|
||||
[Export] public float ZoomSensitivity = 0.1f; // 缩放灵敏度
|
||||
[Export] public Vector2 MinZoom = new Vector2(0.5f, 0.5f); // 最小缩放
|
||||
[Export] public Vector2 MaxZoom = new Vector2(2.0f, 2.0f); // 最大缩放
|
||||
|
||||
private Vector2 _targetPosition;
|
||||
private Vector2 _currentVelocity = Vector2.Zero; // 用于平滑插值
|
||||
private Rect2 _effectiveBounds; // 实际使用的边界(考虑缩放)
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
// 自动查找玩家作为目标
|
||||
Target = GetTree().Root.GetNodeOrNull<Node2D>("Player");
|
||||
if (Target == null)
|
||||
{
|
||||
GD.PrintErr("CameraControl: Failed to find Player node. Assign a Target manually.");
|
||||
}
|
||||
}
|
||||
|
||||
// 确保摄像机位置初始化
|
||||
if (Target != null)
|
||||
{
|
||||
_targetPosition = Target.GlobalPosition + PositionOffset;
|
||||
GlobalPosition = _targetPosition;
|
||||
}
|
||||
|
||||
// 初始化有效边界
|
||||
UpdateEffectiveBounds();
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (Target == null)
|
||||
return;
|
||||
|
||||
float deltaFloat = (float)delta;
|
||||
|
||||
// 更新目标位置(考虑偏移)
|
||||
Vector2 newTarget = Target.GlobalPosition + PositionOffset;
|
||||
|
||||
// 应用死区减少微小移动
|
||||
if (DeadZoneRadius > 0 && newTarget.DistanceTo(_targetPosition) < DeadZoneRadius)
|
||||
{
|
||||
newTarget = _targetPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetPosition = newTarget;
|
||||
}
|
||||
|
||||
// 应用边界限制
|
||||
if (EnableBounds)
|
||||
{
|
||||
UpdateEffectiveBounds();
|
||||
_targetPosition = ClampPosition(_targetPosition);
|
||||
}
|
||||
|
||||
// 应用平滑过渡或直接跟随
|
||||
Vector2 newPosition;
|
||||
if (EnableSmoothing && SmoothingSpeed > 0)
|
||||
{
|
||||
// 使用SmoothDamp获得更自然的缓动效果
|
||||
newPosition = SmoothDamp(GlobalPosition, _targetPosition, ref _currentVelocity, 1 / SmoothingSpeed, deltaFloat);
|
||||
}
|
||||
else
|
||||
{
|
||||
newPosition = _targetPosition;
|
||||
}
|
||||
|
||||
// 最终位置赋值
|
||||
GlobalPosition = newPosition;
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
// 鼠标滚轮缩放控制
|
||||
if (@event is InputEventMouseButton mouseEvent)
|
||||
{
|
||||
if (mouseEvent.IsPressed())
|
||||
{
|
||||
Vector2 zoomFactor = Zoom;
|
||||
|
||||
// // 滚轮向上 - 放大
|
||||
// if (mouseEvent.ButtonIndex == MouseButton.WheelUp)
|
||||
// {
|
||||
// zoomFactor -= Vector2.One * ZoomSensitivity;
|
||||
// }
|
||||
// // 滚轮向下 - 缩小
|
||||
// else if (mouseEvent.ButtonIndex == MouseButton.WheelDown)
|
||||
// {
|
||||
// zoomFactor += Vector2.One * ZoomSensitivity;
|
||||
// }
|
||||
|
||||
// // 钳制缩放值
|
||||
// zoomFactor.X = Mathf.Clamp(zoomFactor.X, MinZoom.X, MaxZoom.X);
|
||||
// zoomFactor.Y = Mathf.Clamp(zoomFactor.Y, MinZoom.Y, MaxZoom.Y);
|
||||
|
||||
if (zoomFactor != Zoom)
|
||||
{
|
||||
Zoom = zoomFactor;
|
||||
UpdateEffectiveBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平滑插值函数(类似Unity的SmoothDamp)
|
||||
/// </summary>
|
||||
private Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, float deltaTime, float maxSpeed = float.MaxValue)
|
||||
{
|
||||
float omega = 2f / Mathf.Max(0.0001f, smoothTime);
|
||||
float x = omega * deltaTime;
|
||||
float exp = 1f / (1f + x + 0.48f * x * x + 0.235f * x * x * x);
|
||||
Vector2 change = current - target;
|
||||
Vector2 originalTarget = target;
|
||||
|
||||
float maxChange = maxSpeed * smoothTime;
|
||||
|
||||
// 使用自定义方法替代ClampMagnitude
|
||||
float magnitude = change.Length();
|
||||
if (magnitude > maxChange && maxChange > 0)
|
||||
{
|
||||
change = change.Normalized() * maxChange;
|
||||
}
|
||||
|
||||
target = current - change;
|
||||
|
||||
Vector2 temp = (currentVelocity + omega * change) * deltaTime;
|
||||
currentVelocity = (currentVelocity - omega * temp) * exp;
|
||||
Vector2 result = target + (change + temp) * exp;
|
||||
|
||||
// 防止过冲
|
||||
Vector2 toOriginalTarget = originalTarget - current;
|
||||
Vector2 toResult = result - originalTarget;
|
||||
if (toOriginalTarget.Dot(toResult) > 0)
|
||||
{
|
||||
result = originalTarget;
|
||||
currentVelocity = (result - originalTarget) / deltaTime;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实际使用的边界(考虑缩放)
|
||||
/// </summary>
|
||||
private void UpdateEffectiveBounds()
|
||||
{
|
||||
if (!EnableBounds || !ScaleWithZoom)
|
||||
{
|
||||
_effectiveBounds = CameraBounds;
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据缩放比例调整边界
|
||||
Vector2 halfViewSize = GetViewportRect().Size * 0.5f / Zoom;
|
||||
Vector2 min = CameraBounds.Position + halfViewSize;
|
||||
Vector2 max = CameraBounds.End - halfViewSize;
|
||||
|
||||
// 确保有效边界有效
|
||||
if (min.X > max.X) min.X = max.X = (min.X + max.X) * 0.5f;
|
||||
if (min.Y > max.Y) min.Y = max.Y = (min.Y + max.Y) * 0.5f;
|
||||
|
||||
_effectiveBounds = new Rect2(min, max - min);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 钳制位置在边界范围内
|
||||
/// </summary>
|
||||
private Vector2 ClampPosition(Vector2 position)
|
||||
{
|
||||
return new Vector2(
|
||||
Mathf.Clamp(position.X, _effectiveBounds.Position.X, _effectiveBounds.End.X),
|
||||
Mathf.Clamp(position.Y, _effectiveBounds.Position.Y, _effectiveBounds.End.Y)
|
||||
);
|
||||
}
|
||||
|
||||
// 调试:在编辑器中绘制边界可视化
|
||||
public override void _Draw()
|
||||
{
|
||||
if (Engine.IsEditorHint() && EnableBounds)
|
||||
{
|
||||
// 绘制主边界
|
||||
var rect = new Rect2(_effectiveBounds.Position - GlobalPosition, _effectiveBounds.Size);
|
||||
DrawRect(rect, Colors.Red, false, 2.0f);
|
||||
|
||||
// 绘制原始边界参考线
|
||||
var origRect = new Rect2(CameraBounds.Position - GlobalPosition, CameraBounds.Size);
|
||||
DrawRect(origRect, Colors.Yellow, false, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置摄像机边界(简化API)
|
||||
/// </summary>
|
||||
public void SetBounds(Rect2 bounds, bool updateImmediately = true)
|
||||
{
|
||||
CameraBounds = bounds;
|
||||
EnableBounds = true;
|
||||
if (updateImmediately)
|
||||
UpdateEffectiveBounds();
|
||||
}
|
||||
}
|
||||
}
|
1
Script/Pawn/CameraControl.cs.uid
Normal file
1
Script/Pawn/CameraControl.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bhpimjcr0h66h
|
232
Script/Pawn/CharacterLoader.cs
Normal file
232
Script/Pawn/CharacterLoader.cs
Normal file
@ -0,0 +1,232 @@
|
||||
using Godot;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
|
||||
namespace Cosmobox
|
||||
{
|
||||
public static class CharacterLoader
|
||||
{
|
||||
// 支持的部件目录列表
|
||||
private static readonly string[] ComponentFolders = {
|
||||
"body", "clothing", "hair", "hairBackground", "head", "leftEar", "rightEar"
|
||||
};
|
||||
|
||||
// 方向关键词映射到索引位置
|
||||
private static readonly Dictionary<string, int> DirectionMapping = new Dictionary<string, int>(System.StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"south", 0}, // 南方向 - 索引0
|
||||
{"north", 1}, // 北方向 - 索引1
|
||||
{"east", 2}, // 东方向 - 索引2
|
||||
{"west", 2} // 西方向也映射到索引2(共用东方向的纹理)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 加载角色所有部件的纹理
|
||||
/// </summary>
|
||||
public static Dictionary<string, Texture2D[]> LoadCharacterTextures(string rootPath)
|
||||
{
|
||||
var textures = new Dictionary<string, Texture2D[]>();
|
||||
// GD.Print($"开始加载角色纹理资源,根目录: {rootPath}");
|
||||
|
||||
// 检查根目录是否存在
|
||||
if (!DirAccess.DirExistsAbsolute(rootPath))
|
||||
{
|
||||
// GD.PrintErr($"错误:资源根目录不存在 - {rootPath}");
|
||||
return textures;
|
||||
}
|
||||
|
||||
using (var dir = DirAccess.Open(rootPath))
|
||||
{
|
||||
if (dir == null)
|
||||
{
|
||||
GD.PrintErr($"错误:无法打开根目录 - {rootPath}");
|
||||
return textures;
|
||||
}
|
||||
|
||||
// GD.Print($"找到根目录,开始扫描部件文件夹...");
|
||||
|
||||
// 遍历所有支持的部件目录
|
||||
foreach (string component in ComponentFolders)
|
||||
{
|
||||
string componentPath = Path.Combine(rootPath, component);
|
||||
// GD.Print($"检查部件: {component},路径: {componentPath}");
|
||||
|
||||
if (DirAccess.DirExistsAbsolute(componentPath))
|
||||
{
|
||||
// GD.Print($"找到部件文件夹: {component}");
|
||||
Texture2D[] componentTextures = LoadComponentTextures(componentPath);
|
||||
|
||||
if (componentTextures != null && componentTextures.Length == 3)
|
||||
{
|
||||
// GD.Print($"成功加载部件: {component},纹理数量: 3");
|
||||
textures[component] = componentTextures;
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"警告:部件{component}纹理加载不完整,实际数量: {componentTextures?.Length ?? 0}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// GD.Print($"部件文件夹不存在: {component}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GD.Print($"资源加载完成,成功部件: {textures.Count}/{ComponentFolders.Length}");
|
||||
return textures;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载单个部件的所有方向纹理
|
||||
/// </summary>
|
||||
private static Texture2D[] LoadComponentTextures(string folderPath)
|
||||
{
|
||||
Texture2D[] textures = new Texture2D[3];
|
||||
var foundFiles = new List<(string path, int index)>();
|
||||
// GD.Print($"加载部件文件夹: {folderPath}");
|
||||
|
||||
using (var dir = DirAccess.Open(folderPath))
|
||||
{
|
||||
if (dir == null)
|
||||
{
|
||||
GD.PrintErr($"错误:无法打开部件目录 - {folderPath}");
|
||||
return textures;
|
||||
}
|
||||
|
||||
var status = dir.ListDirBegin();
|
||||
if (status != Error.Ok)
|
||||
{
|
||||
GD.PrintErr($"错误:无法开始文件列表 ({status}) - {folderPath}");
|
||||
return textures;
|
||||
}
|
||||
|
||||
string fileName = dir.GetNext();
|
||||
int fileCount = 0;
|
||||
int matchedCount = 0;
|
||||
List<string> unmatchedFiles = new List<string>();
|
||||
|
||||
// GD.Print($"开始扫描文件...");
|
||||
|
||||
while (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
fileCount++;
|
||||
if (!dir.CurrentIsDir())
|
||||
{
|
||||
string filePath = Path.Combine(folderPath, fileName);
|
||||
// GD.Print($"处理文件 #{fileCount}: {fileName}");
|
||||
|
||||
// 检查文件扩展名
|
||||
if (!IsImageFile(fileName))
|
||||
{
|
||||
// GD.Print($"跳过非图片文件: {fileName}");
|
||||
fileName = dir.GetNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool matched = false;
|
||||
|
||||
// 检测方向关键词
|
||||
foreach (var kv in DirectionMapping)
|
||||
{
|
||||
if (fileName.Contains(kv.Key, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// GD.Print($"找到方向关键词 '{kv.Key}' -> 索引 {kv.Value}");
|
||||
foundFiles.Add((filePath, kv.Value));
|
||||
matched = true;
|
||||
matchedCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
{
|
||||
GD.PrintErr($"警告:无法识别的方向名称 - {fileName}");
|
||||
unmatchedFiles.Add(fileName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// GD.Print($"跳过子目录: {fileName}");
|
||||
}
|
||||
|
||||
fileName = dir.GetNext();
|
||||
}
|
||||
|
||||
dir.ListDirEnd();
|
||||
|
||||
// GD.Print($"文件夹扫描完成:");
|
||||
// GD.Print($"- 总文件: {fileCount}");
|
||||
// GD.Print($"- 匹配文件: {matchedCount}");
|
||||
|
||||
if (unmatchedFiles.Count > 0)
|
||||
{
|
||||
GD.PrintErr($"警告: {unmatchedFiles.Count} 个文件未包含方向关键词(south/north/east/west):");
|
||||
foreach (var f in unmatchedFiles)
|
||||
{
|
||||
GD.PrintErr($" - {f}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否所有方向都找到了文件
|
||||
bool[] foundDirections = new bool[3];
|
||||
|
||||
// 加载纹理
|
||||
foreach (var (path, index) in foundFiles)
|
||||
{
|
||||
if (index >= 0 && index < textures.Length)
|
||||
{
|
||||
if (textures[index] == null)
|
||||
{
|
||||
// GD.Print($"加载纹理: {Path.GetFileName(path)} -> 方向索引 {index}");
|
||||
textures[index] = ResourceLoader.Load<Texture2D>(path);
|
||||
foundDirections[index] = true;
|
||||
|
||||
if (textures[index] == null)
|
||||
{
|
||||
GD.PrintErr($"错误:无法加载纹理资源 - {path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"跳过纹理: {Path.GetFileName(path)} -> 方向索引 {index}(已有更优先的纹理)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"错误:无效方向索引 {index} - {path}");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查缺失的方向
|
||||
for (int i = 0; i < foundDirections.Length; i++)
|
||||
{
|
||||
if (!foundDirections[i])
|
||||
{
|
||||
string direction;
|
||||
if (i == 0) direction = "south";
|
||||
else if (i == 1) direction = "north";
|
||||
else direction = "east";
|
||||
|
||||
GD.PrintErr($"警告:缺少{direction}方向纹理");
|
||||
}
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查文件是否是图片格式
|
||||
/// </summary>
|
||||
private static bool IsImageFile(string fileName)
|
||||
{
|
||||
string ext = Path.GetExtension(fileName).ToLower();
|
||||
return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".webp" || ext == ".bmp";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1
Script/Pawn/CharacterLoader.cs.uid
Normal file
1
Script/Pawn/CharacterLoader.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cvdj4q7yvd85a
|
252
Script/Pawn/Pawn.cs
Normal file
252
Script/Pawn/Pawn.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Cosmobox
|
||||
{
|
||||
public partial class Pawn : Sprite2D, IThingPhysics
|
||||
{
|
||||
// 移动参数
|
||||
[Export] protected float moveSpeed = 200f; // 每秒移动的世界单位数
|
||||
|
||||
// 角色属性
|
||||
[Export] protected int attack = 10; // 攻击力
|
||||
[Export] protected int health = 100; // 生命值
|
||||
[Export] protected int defense = 5; // 防御力
|
||||
|
||||
// 部件节点
|
||||
[Export] protected Sprite2D head; // 头部精灵
|
||||
[Export] protected Sprite2D body; // 身体精灵
|
||||
[Export] protected Sprite2D clothes; // 衣服精灵
|
||||
[Export] protected Sprite2D hairBackground; // 头发背景精灵
|
||||
[Export] protected Sprite2D hair; // 头发精灵
|
||||
[Export] protected Sprite2D leftEar; // 左耳精灵
|
||||
[Export] protected Sprite2D rightEar; // 右耳精灵
|
||||
|
||||
// 资源路径
|
||||
[Export] protected string characterResourcesPath = null; // 角色资源文件夹路径
|
||||
|
||||
// 纹理数组(每个数组有3个方向:下/上/侧边)
|
||||
[Export] protected Texture2D[] headTextures = new Texture2D[3]; // 头部纹理
|
||||
[Export] protected Texture2D[] bodyTextures = new Texture2D[3]; // 身体纹理
|
||||
[Export] protected Texture2D[] clothesTextures = new Texture2D[3]; // 衣服纹理
|
||||
[Export] protected Texture2D[] hairBackgroundTextures = new Texture2D[3]; // 头发背景纹理
|
||||
[Export] protected Texture2D[] hairTextures = new Texture2D[3]; // 头发纹理
|
||||
[Export] protected Texture2D[] leftEarTextures = new Texture2D[3]; // 左耳纹理
|
||||
[Export] protected Texture2D[] rightEarTextures = new Texture2D[3]; // 右耳纹理
|
||||
|
||||
// 物理相关
|
||||
protected Vector2 currentMovementInput = Vector2.Zero; // 当前原始的输入方向
|
||||
|
||||
public bool Moving
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentMovementInput != Vector2.Zero;
|
||||
}
|
||||
}
|
||||
public override void _Ready()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(characterResourcesPath))
|
||||
{
|
||||
// 加载角色纹理
|
||||
var loadedTextures = CharacterLoader.LoadCharacterTextures(characterResourcesPath);
|
||||
|
||||
// 应用加载的纹理
|
||||
ApplyLoadedTextures(loadedTextures);
|
||||
}
|
||||
|
||||
SetDownDirection(); // 初始化为向下方向
|
||||
}
|
||||
|
||||
void IThingPhysics.PhysicsUpdate(double delta)
|
||||
{
|
||||
float deltaF = (float)delta;
|
||||
|
||||
// 根据输入方向和移动速度直接应用移动
|
||||
// 使用Normalized()确保斜向移动不会更快
|
||||
Position += currentMovementInput.Normalized() * moveSpeed * deltaF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 方向纹理设置方法
|
||||
/// 每个方法设置角色在特定方向的纹理
|
||||
/// </summary>
|
||||
public void SetDownDirection() => SetTextures(0, false); // 设置向下方向的纹理
|
||||
public void SetUpDirection() => SetTextures(1, false); // 设置向上方向的纹理
|
||||
public void SetRightDirection() => SetTextures(2, false); // 设置向右方向的纹理
|
||||
public void SetLeftDirection() => SetTextures(2, true); // 设置向左方向的纹理(使用翻转)
|
||||
|
||||
/// <summary>
|
||||
/// 核心纹理设置方法
|
||||
/// 为角色各部分设置纹理和水平翻转状态
|
||||
/// </summary>
|
||||
/// <param name="index">纹理索引:0=下, 1=上, 2=侧边</param>
|
||||
/// <param name="flipH">是否水平翻转(用于左右方向)</param>
|
||||
private void SetTextures(int index, bool flipH)
|
||||
{
|
||||
// 为每个部件设置纹理
|
||||
if (head != null) head.Texture = headTextures[index];
|
||||
if (body != null) body.Texture = bodyTextures[index];
|
||||
if (clothes != null) clothes.Texture = clothesTextures[index];
|
||||
if (hairBackground != null) hairBackground.Texture = hairBackgroundTextures[index];
|
||||
if (hair != null) hair.Texture = hairTextures[index];
|
||||
// 武器纹理设置(需要时取消注释)
|
||||
// if (weapon != null) weapon.Texture = weaponTextures[index];
|
||||
|
||||
// 设置每个部件的水平翻转状态
|
||||
if (head != null) head.FlipH = flipH;
|
||||
if (body != null) body.FlipH = flipH;
|
||||
if (clothes != null) clothes.FlipH = flipH;
|
||||
if (hairBackground != null) hairBackground.FlipH = flipH;
|
||||
if (hair != null) hair.FlipH = flipH;
|
||||
|
||||
// 耳朵的特殊处理逻辑
|
||||
if (leftEar != null && rightEar != null)
|
||||
{
|
||||
// 先设置耳朵纹理
|
||||
leftEar.Texture = leftEarTextures[index];
|
||||
rightEar.Texture = rightEarTextures[index];
|
||||
|
||||
// 当角色面向侧面时(左右方向)
|
||||
if (index == 2)
|
||||
{
|
||||
if (flipH) // 面向左时
|
||||
{
|
||||
leftEar.Show(); // 显示左耳
|
||||
rightEar.Hide(); // 隐藏右耳
|
||||
}
|
||||
else // 面向右时
|
||||
{
|
||||
leftEar.Hide(); // 隐藏左耳
|
||||
rightEar.Show(); // 显示右耳
|
||||
}
|
||||
}
|
||||
else // 上下方向,两个耳朵都显示
|
||||
{
|
||||
leftEar.Show();
|
||||
rightEar.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简化版移动处理 - 直接存储输入方向
|
||||
/// 实际移动在物理处理过程中完成
|
||||
/// </summary>
|
||||
/// <param name="inputDirection">输入方向向量</param>
|
||||
protected void HandleMovement(Vector2 inputDirection)
|
||||
{
|
||||
// 存储当前输入方向,移动计算在物理过程中处理
|
||||
currentMovementInput = inputDirection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据移动向量更新角色朝向
|
||||
/// </summary>
|
||||
/// <param name="direction">移动方向向量</param>
|
||||
public void UpdateDirection(Vector2 direction)
|
||||
{
|
||||
if (direction == Vector2.Zero) return; // 没有移动时不改变方向
|
||||
|
||||
// 计算角度(0-360度)
|
||||
float angle = Mathf.RadToDeg(Mathf.Atan2(direction.Y, direction.X));
|
||||
if (angle < 0) angle += 360;
|
||||
|
||||
// 根据角度范围决定方向
|
||||
if (angle >= 45 && angle < 135)
|
||||
{
|
||||
SetDownDirection(); // 下方(例如输入 (0, 1))
|
||||
}
|
||||
else if (angle >= 135 && angle < 225)
|
||||
{
|
||||
SetLeftDirection(); // 左方(例如输入 (-1, 0))
|
||||
}
|
||||
else if (angle >= 225 && angle < 315)
|
||||
{
|
||||
SetUpDirection(); // 上方(例如输入 (0, -1))
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRightDirection(); // 右方(例如输入 (1, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用加载的纹理到各个角色部件
|
||||
/// </summary>
|
||||
/// <param name="loadedTextures">加载的纹理字典</param>
|
||||
private void ApplyLoadedTextures(Dictionary<string, Texture2D[]> loadedTextures)
|
||||
{
|
||||
// 头部纹理
|
||||
if (loadedTextures.TryGetValue("head", out var headTex) && headTex.Length == 3)
|
||||
{
|
||||
headTextures = headTex;
|
||||
}
|
||||
|
||||
// 身体纹理
|
||||
if (loadedTextures.TryGetValue("body", out var bodyTex) && bodyTex.Length == 3)
|
||||
{
|
||||
bodyTextures = bodyTex;
|
||||
}
|
||||
|
||||
// 衣服纹理
|
||||
if (loadedTextures.TryGetValue("clothing", out var clothesTex) && clothesTex.Length == 3)
|
||||
{
|
||||
clothesTextures = clothesTex;
|
||||
}
|
||||
|
||||
// 头发纹理
|
||||
if (loadedTextures.TryGetValue("hair", out var hairTex) && hairTex.Length == 3)
|
||||
{
|
||||
hairTextures = hairTex;
|
||||
}
|
||||
|
||||
// 头发背景纹理
|
||||
if (loadedTextures.TryGetValue("hairBackground", out var hairBGTextures) && hairBGTextures.Length == 3)
|
||||
{
|
||||
hairBackgroundTextures = hairBGTextures;
|
||||
}
|
||||
|
||||
// 耳朵纹理 - 分别处理左右耳
|
||||
if (loadedTextures.TryGetValue("leftEar", out var leftEarTex) && leftEarTex.Length == 3)
|
||||
{
|
||||
leftEarTextures = leftEarTex;
|
||||
}
|
||||
|
||||
if (loadedTextures.TryGetValue("rightEar", out var rightEarTex) && rightEarTex.Length == 3)
|
||||
{
|
||||
rightEarTextures = rightEarTex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工具方法
|
||||
/// </summary>
|
||||
|
||||
// 瞬间传送角色到指定位置
|
||||
public void Teleport(Vector2 position) => Position = position;
|
||||
|
||||
// 设置移动速度(确保不小于0)
|
||||
public void SetMoveSpeed(float speed) => moveSpeed = Mathf.Max(speed, 0);
|
||||
|
||||
// 获取当前速度矢量(方向向量 × 速度)
|
||||
public Vector2 GetVelocity()
|
||||
{
|
||||
return currentMovementInput.Normalized() * moveSpeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加瞬间冲量(直接改变位置)
|
||||
/// 注意:对于持续的推动效果可能需要单独实现
|
||||
/// </summary>
|
||||
public void AddImpulse(Vector2 impulse)
|
||||
{
|
||||
// 直接修改位置 - 适用于瞬间位移
|
||||
// 如需持续推动效果,需单独实现冲量系统
|
||||
Position += impulse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
1
Script/Pawn/Pawn.cs.uid
Normal file
1
Script/Pawn/Pawn.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://doim6711upf0a
|
141
Script/Pawn/Player.cs
Normal file
141
Script/Pawn/Player.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic; // 使用 List 和 Dictionary 需要这个命名空间
|
||||
|
||||
namespace Cosmobox
|
||||
{
|
||||
public partial class Player : Pawn, IThing
|
||||
{
|
||||
// === 玩家属性 ===
|
||||
[Export] public Camera2D PlayerCamera; // 玩家摄像机
|
||||
[Export] public Sprite2D AimCursor; // 瞄准光标精灵
|
||||
[Export] public Vector2 AimOffset = Vector2.Zero; // 瞄准点偏移量
|
||||
|
||||
public BagItemList bagItem = new();
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 调用基类的_Ready方法
|
||||
base._Ready();
|
||||
|
||||
// 尝试查找摄像机(如果未分配)
|
||||
if (PlayerCamera == null)
|
||||
{
|
||||
PlayerCamera = GetViewport().GetCamera2D();
|
||||
if (PlayerCamera == null)
|
||||
{
|
||||
GD.PrintErr("Player: 未分配摄像机且视口中没有活动摄像机!");
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化瞄准光标
|
||||
if (AimCursor != null)
|
||||
{
|
||||
AimCursor.Visible = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Update(double delta)
|
||||
{
|
||||
// 获取输入方向
|
||||
Vector2 inputDirection = GetInputDirection();
|
||||
|
||||
// 处理移动(调用Pawn基类方法)
|
||||
HandleMovement(inputDirection);
|
||||
|
||||
// 更新角色朝向(鼠标方向)
|
||||
UpdateDirectionToMouse();
|
||||
}
|
||||
/// <summary>
|
||||
/// 更新角色朝向鼠标方向
|
||||
/// </summary>
|
||||
private void UpdateDirectionToMouse()
|
||||
{
|
||||
if (PlayerCamera == null) return;
|
||||
|
||||
// 获取鼠标在游戏世界中的位置
|
||||
Vector2 mousePosition = GetGlobalMousePosition();
|
||||
|
||||
// 计算从玩家位置到鼠标位置的方向向量
|
||||
Vector2 directionToMouse = (mousePosition - GlobalPosition).Normalized();
|
||||
|
||||
// 调用基类方法更新视觉方向
|
||||
UpdateDirection(directionToMouse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新瞄准光标位置
|
||||
/// </summary>
|
||||
private void UpdateAimCursor()
|
||||
{
|
||||
if (AimCursor == null || PlayerCamera == null) return;
|
||||
|
||||
// 获取鼠标位置
|
||||
Vector2 mousePosition = GetGlobalMousePosition();
|
||||
|
||||
// 设置光标位置(带偏移)
|
||||
AimCursor.GlobalPosition = mousePosition + AimOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取键盘输入方向
|
||||
/// </summary>
|
||||
/// <returns>归一化的移动方向向量</returns>
|
||||
private Vector2 GetInputDirection()
|
||||
{
|
||||
Vector2 direction = Vector2.Zero;
|
||||
|
||||
// 处理上下左右输入(确保项目设置中已配置这些输入)
|
||||
if (Input.IsActionPressed("ui_up"))
|
||||
{
|
||||
direction.Y -= 1; // 上移
|
||||
}
|
||||
if (Input.IsActionPressed("ui_down"))
|
||||
{
|
||||
direction.Y += 1; // 下移
|
||||
}
|
||||
if (Input.IsActionPressed("ui_left"))
|
||||
{
|
||||
direction.X -= 1; // 左移
|
||||
}
|
||||
if (Input.IsActionPressed("ui_right"))
|
||||
{
|
||||
direction.X += 1; // 右移
|
||||
}
|
||||
|
||||
return direction.Normalized(); // 确保斜向移动速度一致
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取瞄准方向(从玩家指向鼠标位置)
|
||||
/// </summary>
|
||||
public Vector2 GetAimDirection()
|
||||
{
|
||||
if (PlayerCamera == null) return Vector2.Zero;
|
||||
|
||||
Vector2 mousePosition = GetGlobalMousePosition();
|
||||
return (mousePosition - GlobalPosition).Normalized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取瞄准角度(弧度)
|
||||
/// </summary>
|
||||
public float GetAimAngle()
|
||||
{
|
||||
Vector2 aimDir = GetAimDirection();
|
||||
return Mathf.Atan2(aimDir.Y, aimDir.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取瞄准角度(度)
|
||||
/// </summary>
|
||||
public float GetAimAngleDegrees()
|
||||
{
|
||||
return Mathf.RadToDeg(GetAimAngle());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
1
Script/Pawn/Player.cs.uid
Normal file
1
Script/Pawn/Player.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c1g503q7yoy1s
|
Reference in New Issue
Block a user