增加注释

This commit is contained in:
wjsjwr 2026-01-11 23:57:24 +08:00
parent 3a3b09c2c5
commit 541ba1695d
50 changed files with 2153 additions and 245 deletions

View File

@ -4,8 +4,8 @@ using Godot;
using Models;
/// <summary>
/// Runtime data for a campus agent. This keeps Godot nodes and pure data separate
/// so the behavior system can be tested without scene dependencies.
/// 校园代理的运行时数据。将 Godot 节点与纯数据分离,
/// 以便行为系统可以在没有场景依赖的情况下进行测试。
/// </summary>
public sealed class CampusAgentRuntime
{
@ -34,8 +34,8 @@ public sealed class CampusAgentRuntime
}
/// <summary>
/// Intent produced by the planner. It captures both the action and the destination,
/// plus an optional planned duration for round-based schedules.
/// 规划器生成的意图。它捕获了行动和目的地,
/// 加上可选的轮次计划持续时间。
/// </summary>
public sealed class CampusBehaviorIntent
{
@ -62,8 +62,8 @@ public sealed class CampusBehaviorIntent
}
/// <summary>
/// Shared context passed into providers/states so they can evaluate the same data
/// without hard-coding dependencies.
/// 传递给提供者/状态的共享上下文,以便它们可以评估相同的数据
/// 而无需硬编码依赖关系。
/// </summary>
public sealed class CampusBehaviorContext
{
@ -92,8 +92,8 @@ public sealed class CampusBehaviorContext
}
/// <summary>
/// Providers represent a single rule in the priority queue. Each provider returns
/// a behavior intent or null if it cannot apply to the current context.
/// 提供者表示优先级队列中的单个规则。
/// 每个提供者返回一个行为意图,或者如果它不能应用于当前上下文,则返回 null。
/// </summary>
public interface ICampusBehaviorProvider
{
@ -101,8 +101,8 @@ public interface ICampusBehaviorProvider
}
/// <summary>
/// Critical state provider: handles sanity collapse, extreme stress, or exhaustion.
/// This is the highest priority in the decision queue.
/// 紧急状态提供者:处理理智崩溃、极度压力或力竭。
/// 这是决策队列中的最高优先级。
/// </summary>
public sealed class CriticalBehaviorProvider : ICampusBehaviorProvider
{
@ -153,7 +153,7 @@ public sealed class CriticalBehaviorProvider : ICampusBehaviorProvider
}
/// <summary>
/// Assigned task provider: if the agent has a task, it is executed before needs.
/// 指派任务提供者:如果代理有任务,则在需求之前执行。
/// </summary>
public sealed class AssignedTaskBehaviorProvider : ICampusBehaviorProvider
{
@ -188,8 +188,8 @@ public sealed class AssignedTaskBehaviorProvider : ICampusBehaviorProvider
}
/// <summary>
/// Needs provider: hunger, fatigue, mood, and social needs are handled here.
/// It sits below assigned tasks but above trait-driven idle behavior.
/// 需求提供者:处理饥饿、疲劳、情绪和社交需求。
/// 它位于指派任务之下,但在特质驱动的空闲行为之上。
/// </summary>
public sealed class NeedsBehaviorProvider : ICampusBehaviorProvider
{
@ -273,8 +273,7 @@ public sealed class NeedsBehaviorProvider : ICampusBehaviorProvider
}
/// <summary>
/// Trait-driven provider: applies long-term personality or tag tendencies when
/// there is no urgent need.
/// 特质驱动提供者:当没有紧急需求时,应用长期性格或标签倾向。
/// </summary>
public sealed class TraitBehaviorProvider : ICampusBehaviorProvider
{
@ -405,7 +404,7 @@ public sealed class TraitBehaviorProvider : ICampusBehaviorProvider
}
/// <summary>
/// Idle provider: default fallback when nothing else applies.
/// 闲置提供者:当没有其他适用规则时的默认后备方案。
/// </summary>
public sealed class IdleBehaviorProvider : ICampusBehaviorProvider
{
@ -420,8 +419,8 @@ public sealed class IdleBehaviorProvider : ICampusBehaviorProvider
}
/// <summary>
/// Planner executes providers in priority order. This lets us add or remove
/// providers without editing the state machine.
/// 规划器按优先级顺序执行提供者。这允许我们添加或删除提供者
/// 而无需编辑状态机。
/// </summary>
public sealed class CampusBehaviorPlanner
{
@ -448,8 +447,7 @@ public sealed class CampusBehaviorPlanner
}
/// <summary>
/// State interface for the AI FSM. Each state can transition by requesting
/// a change via the owning behavior agent.
/// AI FSM 的状态接口。每个状态可以通过请求更改来转换。
/// </summary>
public interface ICampusBehaviorState
{
@ -459,7 +457,7 @@ public interface ICampusBehaviorState
}
/// <summary>
/// State machine wrapper to enforce enter/exit semantics.
/// 状态机包装器,用于强制执行进入/退出语义。
/// </summary>
public sealed class CampusBehaviorStateMachine
{
@ -479,8 +477,8 @@ public sealed class CampusBehaviorStateMachine
}
/// <summary>
/// Decision state: pick a new intent and immediately transition to movement.
/// This keeps the intent selection isolated and easy to extend.
/// 决策状态:选择一个新的意图并立即转换为移动。
/// 这使意图选择隔离且易于扩展。
/// </summary>
public sealed class CampusDecisionState : ICampusBehaviorState
{
@ -500,8 +498,8 @@ public sealed class CampusDecisionState : ICampusBehaviorState
}
/// <summary>
/// Movement state: navigate to the intent's target location.
/// Once the agent arrives, it transitions into the action state.
/// 移动状态:导航到意图的目标位置。
/// 一旦代理到达,它将转换为动作状态。
/// </summary>
public sealed class CampusMoveState : ICampusBehaviorState
{
@ -534,8 +532,8 @@ public sealed class CampusMoveState : ICampusBehaviorState
}
/// <summary>
/// Action state: apply per-second deltas and update task progress.
/// When the action duration expires, transition back to decision.
/// 动作状态:应用每秒增量并更新任务进度。
/// 当动作持续时间结束时,转回决策状态。
/// </summary>
public sealed class CampusActionState : ICampusBehaviorState
{
@ -642,8 +640,8 @@ public sealed class CampusActionState : ICampusBehaviorState
}
/// <summary>
/// Main behavior agent that drives one campus character. It owns the planner,
/// state machine, and applies baseline stat decay on every tick.
/// 驱动一个校园角色的主要行为代理。它拥有规划器、
/// 状态机,并在每次 Tick 时应用基线属性衰减。
/// </summary>
public sealed class CampusBehaviorAgent
{
@ -1088,8 +1086,8 @@ public sealed class CampusBehaviorAgent
}
/// <summary>
/// Centralized IDs for traits referenced by the behavior system.
/// Keeping them here avoids scattering magic strings.
/// 行为系统引用的特质的集中 ID。
/// 将它们放在这里可以避免分散的魔法字符串。
/// </summary>
public static class CampusTraitIds
{
@ -1098,4 +1096,4 @@ public static class CampusTraitIds
public const string SocialButterfly = "core:trait_social_butterfly";
public const string NotHuman = "core:trait_not_human";
public const string BigEater = "core:trait_big_eater";
}
}

View File

@ -7,72 +7,72 @@ using Godot;
using Models;
/// <summary>
/// Location identifiers used by the campus behavior system.
/// These map to Node2D markers in campus.tscn so the AI can pick targets by name.
/// 校园行为系统使用的位置标识符。
/// 这些标识符映射到 campus.tscn 中的 Node2D 标记点,以便 AI 可以通过名称选择目标。
/// </summary>
public enum CampusLocationId
{
None,
Laboratory,
Library,
Canteen,
Dormitory,
ArtificialLake,
CoffeeShop,
AdministrationBuilding,
FootballField,
RandomWander
Laboratory, // 实验室
Library, // 图书馆
Canteen, // 食堂
Dormitory, // 宿舍
ArtificialLake, // 人工湖
CoffeeShop, // 咖啡店
AdministrationBuilding, // 行政楼
FootballField, // 足球场
RandomWander // 随机漫游
}
/// <summary>
/// Action identifiers used by the behavior planner and state machine.
/// Each action is configured via campus_behavior.json for duration and stat deltas.
/// 行为规划器和状态机使用的动作标识符。
/// 每个动作通过 campus_behavior.json 配置持续时间和属性变化。
/// </summary>
public enum CampusActionId
{
None,
Experimenting,
Writing,
Eating,
Sleeping,
Chilling,
Staring,
CoffeeBreak,
Administration,
Running,
Socializing,
Wandering
Experimenting, // 做实验
Writing, // 写作
Eating, // 吃饭
Sleeping, // 睡觉
Chilling, // 放松/闲逛
Staring, // 发呆
CoffeeBreak, // 喝咖啡
Administration, // 行政工作
Running, // 跑步
Socializing, // 社交
Wandering // 漫步
}
/// <summary>
/// Priority levels match the design doc ordering: lower value = higher priority.
/// 优先级级别,对应设计文档中的顺序:值越小优先级越高。
/// </summary>
public enum CampusBehaviorPriority
{
Critical = 0,
AssignedTask = 1,
Needs = 2,
Trait = 3,
Idle = 4
Critical = 0, // 紧急状态(崩溃/力竭)
AssignedTask = 1, // 指派任务
Needs = 2, // 基础需求(饿/累/社交)
Trait = 3, // 特质驱动(性格偏好)
Idle = 4 // 闲置
}
/// <summary>
/// Minimal task types for the campus demo. These are not full gameplay tasks,
/// just drivers for the assigned-task priority in the AI.
/// 校园演示的最小化任务类型。这些不是完整的游戏任务,
/// 仅用于驱动 AI 的“指派任务”优先级。
/// </summary>
public enum CampusTaskType
{
Experiment,
Writing,
Administration,
Exercise,
Coding,
Social
Experiment, // 实验
Writing, // 写作
Administration, // 行政
Exercise, // 锻炼
Coding, // 编程
Social // 社交
}
/// <summary>
/// Action configuration loaded from JSON. Deltas are applied per second while
/// the action is running, so longer actions accumulate more effect.
/// 从 JSON 加载的动作配置。
/// 变化量Delta在动作运行时按秒应用因此动作越长积累的效果越多。
/// </summary>
public sealed class CampusActionConfig
{
@ -90,8 +90,8 @@ public sealed class CampusActionConfig
}
/// <summary>
/// Global behavior configuration for campus AI. This is intentionally data-driven
/// so balancing can happen in JSON without touching code.
/// 校园 AI 的全局行为配置。
/// 这是数据驱动的,以便在不修改代码的情况下通过 JSON 进行平衡调整。
/// </summary>
public sealed class CampusBehaviorConfig
{
@ -179,8 +179,8 @@ public sealed class CampusBehaviorConfig
}
/// <summary>
/// Simple location registry that maps logical location ids to scene positions.
/// This keeps the behavior system independent from scene tree details.
/// 简单的位置注册表,将逻辑位置 ID 映射到场景位置。
/// 保持行为系统独立于场景树细节。
/// </summary>
public sealed class CampusLocationRegistry
{
@ -199,8 +199,8 @@ public sealed class CampusLocationRegistry
}
/// <summary>
/// Tracks current occupancy per location so traits like social phobia can react
/// to crowd size without hard-coding scene knowledge.
/// 跟踪每个位置的当前占用情况,以便像社交恐惧症这样的特质可以根据人群规模做出反应,
/// 而无需硬编码场景知识。
/// </summary>
public sealed class CampusBehaviorWorld
{
@ -229,7 +229,7 @@ public sealed class CampusBehaviorWorld
}
/// <summary>
/// Lightweight task container for the campus demo; it just tracks remaining work.
/// 校园演示的轻量级任务容器;它只跟踪剩余工作量。
/// </summary>
public sealed class CampusTask
{
@ -251,8 +251,8 @@ public sealed class CampusTask
}
/// <summary>
/// Custom needs that are not yet part of the core UnitModel (hunger/social/energy).
/// Uses PropertyValue so it plugs into the existing numeric system.
/// 自定义需求,尚未成为核心 UnitModel 的一部分(饥饿/社交/精力)。
/// 使用 PropertyValue 以便接入现有的数值系统。
/// </summary>
public sealed class CampusAgentNeeds
{
@ -268,4 +268,4 @@ public sealed class CampusAgentNeeds
Social = new PropertyValue(social);
Health = new PropertyValue(health);
}
}
}

View File

@ -3,63 +3,238 @@ using System.Collections.Generic;
using System.Diagnostics;
using Godot;
/// <summary>
/// 校园学生角色控制器
/// </summary>
public partial class CampusStudent : CharacterBody2D
{
/// <summary>
/// 饰品精灵
/// </summary>
private Sprite2D _accessory;
/// <summary>
/// 动画播放器
/// </summary>
private AnimationPlayer _animationPlayer;
/// <summary>
/// 身体精灵
/// </summary>
private Sprite2D _body;
/// <summary>
/// 眼睛精灵
/// </summary>
private Sprite2D _eye;
/// <summary>
/// 发型精灵
/// </summary>
private Sprite2D _hairstyle;
/// <summary>
/// 上一帧的时间间隔
/// </summary>
private double _lastDelta;
/// <summary>
/// 面朝方向
/// </summary>
private enum FacingDirection
{
Up,
Down,
Left,
Right
}
/// <summary>
/// 上一次的面朝方向
/// </summary>
private FacingDirection _lastFacing = FacingDirection.Down;
/// <summary>
/// 上一次的位置
/// </summary>
private Vector2 _lastPosition;
/// <summary>
/// 导航代理
/// </summary>
private NavigationAgent2D _navigationAgent;
/// <summary>
/// 服装精灵
/// </summary>
private Sprite2D _outfit;
/// <summary>
/// 是否已配置巡逻
/// </summary>
private bool _patrolConfigured;
/// <summary>
/// 当前巡逻点索引
/// </summary>
private int _patrolIndex;
/// <summary>
/// 巡逻点列表
/// </summary>
private List<Vector2> _patrolPoints = new();
/// <summary>
/// 手机精灵
/// </summary>
private Sprite2D _smartphone;
/// <summary>
/// 卡住计时器
/// </summary>
private float _stuckTimer;
/// <summary>
/// 导航地图RID
/// </summary>
private Rid _navigationMap;
/// <summary>
/// 当前目标点
/// </summary>
private Vector2 _currentTarget = Vector2.Zero;
/// <summary>
/// 是否有目标
/// </summary>
private bool _hasTarget;
/// <summary>
/// 是否启用行为控制
/// </summary>
private bool _behaviorControlEnabled;
/// <summary>
/// 行为目标点
/// </summary>
private Vector2 _behaviorTarget = Vector2.Zero;
/// <summary>
/// 是否有行为目标
/// </summary>
private bool _behaviorHasTarget;
/// <summary>
/// 手机闲置动画是否激活
/// </summary>
private bool _phoneIdleActive;
/// <summary>
/// 手机退出动作锁定
/// </summary>
private bool _phoneExitLocked;
/// <summary>
/// 是否使用物理移动
/// </summary>
private bool _usePhysicsMovement = true;
/// <summary>
/// 网格路径列表
/// </summary>
private readonly List<Vector2> _gridPath = new();
/// <summary>
/// 当前网格路径索引
/// </summary>
private int _gridPathIndex;
/// <summary>
/// 网格路径是否激活
/// </summary>
private bool _gridPathActive;
/// <summary>
/// 网格路径是否挂起
/// </summary>
private bool _gridPathPending;
/// <summary>
/// AStar网格
/// </summary>
private AStarGrid2D _astarGrid;
/// <summary>
/// AStar区域
/// </summary>
private Rect2I _astarRegion;
/// <summary>
/// AStar地图迭代版本
/// </summary>
private int _astarMapIteration;
/// <summary>
/// 网格重新寻路重试计时器
/// </summary>
private float _gridPathRetryTimer;
/// <summary>
/// 导航区域引用
/// </summary>
private NavigationRegion2D _navigationRegion;
/// <summary>
/// 移动速度
/// </summary>
[Export] public float MoveSpeed { get; set; } = 60.0f;
/// <summary>
/// 目标到达判定距离
/// </summary>
[Export] public float TargetReachDistance { get; set; } = 6.0f;
/// <summary>
/// 是否使用16x16精灵
/// </summary>
[Export] public bool Use16X16Sprites { get; set; } = true;
/// <summary>
/// 是否启用避让
/// </summary>
[Export] public bool EnableAvoidance { get; set; }
/// <summary>
/// 卡住重新寻路时间
/// </summary>
[Export] public float StuckRepathSeconds { get; set; } = 0.6f;
/// <summary>
/// 卡住距离阈值
/// </summary>
[Export] public float StuckDistanceEpsilon { get; set; } = 2.0f;
/// <summary>
/// 导航网格吸附距离
/// </summary>
[Export] public float NavMeshClampDistance { get; set; } = 6.0f;
/// <summary>
/// 是否使用网格寻路
/// </summary>
[Export] public bool UseGridPathfinding { get; set; } = true;
/// <summary>
/// 网格单元大小
/// </summary>
[Export] public float GridCellSize { get; set; } = 8.0f;
/// <summary>
/// 网格可行走容差
/// </summary>
[Export] public float GridWalkableTolerance { get; set; } = 2.0f;
/// <summary>
/// 网格搜索节点限制
/// </summary>
[Export] public int GridSearchNodeLimit { get; set; } = 8000;
/// <summary>
/// 网格重新寻路间隔
/// </summary>
[Export] public float GridRepathInterval { get; set; } = 0.25f;
/// <summary>
/// 调试绘制网格
/// </summary>
[Export] public bool DebugDrawGrid { get; set; }
/// <summary>
/// 调试仅绘制实心点
/// </summary>
[Export] public bool DebugDrawSolidOnly { get; set; } = true;
/// <summary>
/// 调试绘制路径
/// </summary>
[Export] public bool DebugDrawPath { get; set; }
/// <summary>
/// 调试绘制半径单元数
/// </summary>
[Export] public int DebugDrawRadiusCells { get; set; } = 20;
/// <summary>
/// 调试日志网格
/// </summary>
[Export] public bool DebugLogGrid { get; set; }
/// <summary>
/// 环境碰撞掩码
/// </summary>
[Export] public uint EnvironmentCollisionMask { get; set; } = 1u;
/// <summary>
/// 学生碰撞层
/// </summary>
[Export] public uint StudentCollisionLayer { get; set; } = 1u << 1;
/// <summary>
/// 准备就绪时调用
/// </summary>
public override void _Ready()
{
_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");
@ -94,6 +269,10 @@ public partial class CampusStudent : CharacterBody2D
_lastPosition = GlobalPosition;
}
/// <summary>
/// 物理处理
/// </summary>
/// <param name="delta">时间间隔</param>
public override void _PhysicsProcess(double delta)
{
_lastDelta = delta;
@ -212,6 +391,9 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 绘制调试信息
/// </summary>
public override void _Draw()
{
if (!DebugDrawGrid && !DebugDrawPath) return;
@ -269,6 +451,11 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 配置巡逻
/// </summary>
/// <param name="points">巡逻点列表</param>
/// <param name="startIndex">起始索引</param>
public void ConfigurePatrol(List<Vector2> points, int startIndex)
{
_patrolPoints = points ?? new List<Vector2>();
@ -281,6 +468,9 @@ public partial class CampusStudent : CharacterBody2D
if (_navigationAgent != null) AdvanceTarget();
}
/// <summary>
/// 启用行为控制
/// </summary>
public void EnableBehaviorControl()
{
_behaviorControlEnabled = true;
@ -289,6 +479,10 @@ public partial class CampusStudent : CharacterBody2D
_patrolPoints.Clear();
}
/// <summary>
/// 设置行为目标
/// </summary>
/// <param name="target">目标位置</param>
public void SetBehaviorTarget(Vector2 target)
{
_behaviorControlEnabled = true;
@ -316,6 +510,9 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 清除行为目标
/// </summary>
public void ClearBehaviorTarget()
{
_behaviorHasTarget = false;
@ -329,6 +526,10 @@ public partial class CampusStudent : CharacterBody2D
_gridPath.Clear();
}
/// <summary>
/// 是否已到达行为目标
/// </summary>
/// <returns>到达返回true</returns>
public bool HasReachedBehaviorTarget()
{
if (!_behaviorHasTarget) return true;
@ -344,6 +545,9 @@ public partial class CampusStudent : CharacterBody2D
return _navigationAgent.IsNavigationFinished();
}
/// <summary>
/// 开始玩手机
/// </summary>
public void StartPhoneIdle()
{
if (_animationPlayer == null || !_animationPlayer.HasAnimation("phone_up")) return;
@ -354,6 +558,10 @@ public partial class CampusStudent : CharacterBody2D
_animationPlayer.Play("phone_up");
}
/// <summary>
/// 停止玩手机
/// </summary>
/// <param name="lockMovement">是否锁定移动</param>
public void StopPhoneIdle(bool lockMovement = false)
{
if (_animationPlayer == null)
@ -386,6 +594,10 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 设置导航地图
/// </summary>
/// <param name="map">地图RID</param>
public void SetNavigationMap(Rid map)
{
// 由校园控制器传入导航地图,供本地边界夹紧使用
@ -396,6 +608,9 @@ public partial class CampusStudent : CharacterBody2D
_navigationRegion = FindNavigationRegion();
}
/// <summary>
/// 应用随机主题
/// </summary>
public void ApplyRandomTheme()
{
// 随机替换身体与配件贴图,形成不同主题外观
@ -410,6 +625,9 @@ public partial class CampusStudent : CharacterBody2D
_smartphone.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Phone, Use16X16Sprites));
}
/// <summary>
/// 缓存精灵节点
/// </summary>
private void CacheSprites()
{
// 缓存子节点引用,避免每帧查找
@ -421,6 +639,9 @@ public partial class CampusStudent : CharacterBody2D
_smartphone = GetNode<Sprite2D>("parts/smartphone");
}
/// <summary>
/// 配置碰撞
/// </summary>
private void ConfigureCollision()
{
// 学生只与环境碰撞,不与其它学生碰撞
@ -429,6 +650,9 @@ public partial class CampusStudent : CharacterBody2D
_usePhysicsMovement = true;
}
/// <summary>
/// 推进到下一个目标
/// </summary>
private void AdvanceTarget()
{
if (_patrolPoints.Count == 0 || _navigationAgent == null) return;
@ -455,6 +679,10 @@ public partial class CampusStudent : CharacterBody2D
_stuckTimer = 0.0f;
}
/// <summary>
/// 计算避让速度时的回调
/// </summary>
/// <param name="safeVelocity">安全速度</param>
private void OnVelocityComputed(Vector2 safeVelocity)
{
if (_phoneExitLocked)
@ -473,6 +701,10 @@ public partial class CampusStudent : CharacterBody2D
UpdateStuckTimer(_lastDelta);
}
/// <summary>
/// 更新卡住计时器
/// </summary>
/// <param name="delta">时间间隔</param>
private void UpdateStuckTimer(double delta)
{
if (StuckRepathSeconds <= 0.0f || !_hasTarget)
@ -496,6 +728,9 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 重新规划路径到当前目标
/// </summary>
private void RepathToCurrentTarget()
{
if (!_hasTarget) return;
@ -516,6 +751,9 @@ public partial class CampusStudent : CharacterBody2D
_navigationAgent.TargetPosition = _currentTarget;
}
/// <summary>
/// 尝试构建挂起的网格路径
/// </summary>
private void TryBuildPendingGridPath()
{
if (!UseGridPathfinding || !_gridPathPending) return;
@ -540,11 +778,20 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 导航地图是否准备就绪
/// </summary>
/// <returns>准备就绪返回true</returns>
private bool IsNavigationMapReady()
{
return _navigationMap.IsValid && NavigationServer2D.MapGetIterationId(_navigationMap) > 0;
}
/// <summary>
/// 将速度限制在导航网格内
/// </summary>
/// <param name="velocity">目标速度</param>
/// <returns>限制后的速度</returns>
private Vector2 ClampVelocityToNavMesh(Vector2 velocity)
{
if (!IsNavigationMapReady()) return velocity;
@ -566,6 +813,11 @@ public partial class CampusStudent : CharacterBody2D
return velocity;
}
/// <summary>
/// 将目标点限制在导航网格内
/// </summary>
/// <param name="target">目标点</param>
/// <returns>限制后的目标点</returns>
private Vector2 ClampTargetToNavMesh(Vector2 target)
{
if (!IsNavigationMapReady()) return target;
@ -579,6 +831,10 @@ public partial class CampusStudent : CharacterBody2D
return closest;
}
/// <summary>
/// 应用移动
/// </summary>
/// <param name="delta">时间间隔</param>
private void ApplyMovement(double delta)
{
if (_usePhysicsMovement)
@ -591,6 +847,10 @@ public partial class CampusStudent : CharacterBody2D
GlobalPosition += Velocity * (float)delta;
}
/// <summary>
/// 更新面朝向动画
/// </summary>
/// <param name="velocity">当前速度</param>
private void UpdateFacingAnimation(Vector2 velocity)
{
if (_phoneIdleActive || _phoneExitLocked)
@ -626,6 +886,9 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 播放闲置动画
/// </summary>
private void PlayIdleAnimation()
{
if (_phoneIdleActive || _phoneExitLocked)
@ -650,6 +913,10 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 播放指定动画
/// </summary>
/// <param name="animationName">动画名称</param>
private void PlayAnimation(string animationName)
{
if (_animationPlayer == null) return;
@ -657,6 +924,10 @@ public partial class CampusStudent : CharacterBody2D
if (_animationPlayer.CurrentAnimation != animationName) _animationPlayer.Play(animationName);
}
/// <summary>
/// 动画结束回调
/// </summary>
/// <param name="animationName">动画名称</param>
private void OnAnimationFinished(StringName animationName)
{
if (_animationPlayer == null) return;
@ -677,6 +948,11 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 处理网格路径移动
/// </summary>
/// <param name="delta">时间间隔</param>
/// <returns>如果正在移动返回true</returns>
private bool ProcessGridPathMovement(double delta)
{
if (_gridPathIndex >= _gridPath.Count)
@ -730,6 +1006,11 @@ public partial class CampusStudent : CharacterBody2D
return true;
}
/// <summary>
/// 转换为轴向速度
/// </summary>
/// <param name="delta">位移</param>
/// <returns>轴向速度</returns>
private Vector2 ToAxisVelocity(Vector2 delta)
{
if (Mathf.Abs(delta.X) >= Mathf.Abs(delta.Y))
@ -740,6 +1021,12 @@ public partial class CampusStudent : CharacterBody2D
return new Vector2(0, Mathf.Sign(delta.Y));
}
/// <summary>
/// 构建网格路径
/// </summary>
/// <param name="start">起点</param>
/// <param name="target">终点</param>
/// <returns>路径点列表</returns>
private List<Vector2> BuildGridPath(Vector2 start, Vector2 target)
{
if (!IsNavigationMapReady() || GridCellSize <= 0.0f)
@ -809,6 +1096,10 @@ public partial class CampusStudent : CharacterBody2D
return SimplifyGridPath(result);
}
/// <summary>
/// 移除直接回头的路径点
/// </summary>
/// <param name="path">路径</param>
private void RemoveImmediateBacktracks(List<Vector2> path)
{
if (path == null || path.Count < 3) return;
@ -826,6 +1117,11 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 简化网格路径(移除共线点)
/// </summary>
/// <param name="path">路径</param>
/// <returns>简化后的路径</returns>
private List<Vector2> SimplifyGridPath(List<Vector2> path)
{
if (path == null || path.Count < 3) return path;
@ -863,6 +1159,9 @@ public partial class CampusStudent : CharacterBody2D
return simplified;
}
/// <summary>
/// 确保AStar网格已建立
/// </summary>
private void EnsureAStarGrid()
{
if (!IsNavigationMapReady()) return;
@ -916,6 +1215,10 @@ public partial class CampusStudent : CharacterBody2D
}
}
/// <summary>
/// 构建网格区域
/// </summary>
/// <returns>网格区域</returns>
private Rect2I BuildGridRegion()
{
var bounds = BuildWorldBounds();
@ -929,6 +1232,10 @@ public partial class CampusStudent : CharacterBody2D
return new Rect2I(new Vector2I(minX, minY), new Vector2I(sizeX, sizeY));
}
/// <summary>
/// 构建世界边界
/// </summary>
/// <returns>世界边界</returns>
private Rect2 BuildWorldBounds()
{
var viewport = GetViewportRect();
@ -979,6 +1286,11 @@ public partial class CampusStudent : CharacterBody2D
return bounds.Merge(navBounds);
}
/// <summary>
/// 单元格是否在区域内
/// </summary>
/// <param name="cell">单元格</param>
/// <returns>如果在区域内返回true</returns>
private bool IsCellInRegion(Vector2I cell)
{
return cell.X >= _astarRegion.Position.X
@ -987,6 +1299,11 @@ public partial class CampusStudent : CharacterBody2D
&& cell.Y < _astarRegion.Position.Y + _astarRegion.Size.Y;
}
/// <summary>
/// 单元格是否在边界内
/// </summary>
/// <param name="cell">单元格</param>
/// <returns>如果在边界内返回true</returns>
private bool IsCellInBounds(Vector2I cell)
{
if (_astarGrid != null)
@ -997,6 +1314,11 @@ public partial class CampusStudent : CharacterBody2D
return IsCellInRegion(cell);
}
/// <summary>
/// 世界坐标转网格坐标
/// </summary>
/// <param name="world">世界坐标</param>
/// <returns>网格坐标</returns>
private Vector2I WorldToGrid(Vector2 world)
{
var size = Mathf.Max(1.0f, GridCellSize);
@ -1005,12 +1327,23 @@ public partial class CampusStudent : CharacterBody2D
Mathf.FloorToInt(world.Y / size));
}
/// <summary>
/// 网格坐标转世界坐标
/// </summary>
/// <param name="grid">网格坐标</param>
/// <returns>世界坐标</returns>
private Vector2 GridToWorld(Vector2I grid)
{
var size = Mathf.Max(1.0f, GridCellSize);
return new Vector2((grid.X + 0.5f) * size, (grid.Y + 0.5f) * size);
}
/// <summary>
/// 查找最近的开放单元格
/// </summary>
/// <param name="origin">起点</param>
/// <param name="radius">搜索半径</param>
/// <returns>最近的开放单元格</returns>
private Vector2I FindNearestOpenCell(Vector2I origin, int radius)
{
if (_astarGrid == null) return origin;
@ -1039,6 +1372,11 @@ public partial class CampusStudent : CharacterBody2D
return origin;
}
/// <summary>
/// 世界坐标是否可行走
/// </summary>
/// <param name="world">世界坐标</param>
/// <returns>如果可行走返回true</returns>
private bool IsWorldWalkable(Vector2 world)
{
if (!IsNavigationMapReady()) return false;
@ -1046,11 +1384,20 @@ public partial class CampusStudent : CharacterBody2D
return closest.DistanceTo(world) <= GetGridTolerance();
}
/// <summary>
/// 查找导航区域节点
/// </summary>
/// <returns>导航区域</returns>
private NavigationRegion2D FindNavigationRegion()
{
return GetTree()?.CurrentScene?.FindChild("NavigationRegion2D", true, false) as NavigationRegion2D;
}
/// <summary>
/// 单元格中心是否可行走
/// </summary>
/// <param name="center">中心点</param>
/// <returns>如果可行走返回true</returns>
private bool IsCellWalkable(Vector2 center)
{
if (!IsWorldWalkable(center)) return false;
@ -1079,16 +1426,12 @@ public partial class CampusStudent : CharacterBody2D
return true;
}
/// <summary>
/// 获取网格容差
/// </summary>
/// <returns>网格容差</returns>
private float GetGridTolerance()
{
return Mathf.Max(1.0f, GridWalkableTolerance);
}
private enum FacingDirection
{
Up,
Down,
Left,
Right
}
}
}

View File

@ -10,8 +10,15 @@ namespace Core;
[GlobalClass]
public partial class ContentCollectionResource : Resource, IContentResourceCollection
{
/// <summary>
/// 资源列表
/// </summary>
[Export] public Array<Resource> Items { get; set; } = new();
/// <summary>
/// 获取资源项
/// </summary>
/// <returns>资源项集合</returns>
public IEnumerable<IContentResource> GetItems()
{
foreach (var item in Items)
@ -22,5 +29,4 @@ public partial class ContentCollectionResource : Resource, IContentResourceColle
}
}
}
}
}

View File

@ -20,27 +20,52 @@ namespace Core;
/// </summary>
public interface IContentSource
{
/// <summary>
/// 优先级
/// </summary>
int Priority { get; }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
IEnumerable<T> LoadAll<T>() where T : class;
}
/// <summary>
/// 内容合并模式
/// </summary>
public enum ContentMergeMode
{
Override,
KeepFirst
Override, // 覆盖
KeepFirst // 保留首次
}
/// <summary>
/// 内容注册表
/// </summary>
public sealed class ContentRegistry
{
private readonly List<IContentSource> _sources = new();
/// <summary>
/// 合并模式
/// </summary>
public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override;
/// <summary>
/// 注册内容源
/// </summary>
/// <param name="source">内容源</param>
public void RegisterSource(IContentSource source)
{
_sources.Add(source);
_sources.Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
/// <summary>
/// 构建游戏内容数据库
/// </summary>
/// <returns>游戏内容数据库</returns>
public GameContentDatabase BuildDatabase()
{
var db = new GameContentDatabase();
@ -55,6 +80,11 @@ public sealed class ContentRegistry
return db;
}
/// <summary>
/// 从所有源加载指定类型的所有对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
private IEnumerable<T> LoadAll<T>() where T : class
{
foreach (var source in _sources)
@ -66,6 +96,13 @@ public sealed class ContentRegistry
}
}
/// <summary>
/// 合并对象到目标字典
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="target">目标字典</param>
/// <param name="items">对象集合</param>
/// <param name="idSelector">ID选择器</param>
private void Merge<T>(Dictionary<string, T> target, IEnumerable<T> items, Func<T, string> idSelector) where T : class
{
foreach (var item in items)
@ -97,14 +134,29 @@ public sealed class ContentRegistry
/// </summary>
public sealed class ResourceContentSource : IContentSource
{
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; }
/// <summary>
/// 资源路径列表
/// </summary>
public List<string> ResourcePaths { get; } = new();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public ResourceContentSource(int priority)
{
Priority = priority;
}
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class
{
foreach (var path in ResourcePaths)
@ -127,6 +179,12 @@ public sealed class ResourceContentSource : IContentSource
}
}
/// <summary>
/// 从资源中提取对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="resource">资源</param>
/// <returns>对象集合</returns>
private IEnumerable<T> ExtractResources<T>(Resource resource) where T : class
{
if (resource is IContentResource content)
@ -163,10 +221,20 @@ public sealed class ResourceContentSource : IContentSource
/// </summary>
public sealed class JsonContentSource : IContentSource
{
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; }
/// <summary>
/// 数据路径列表
/// </summary>
public List<string> DataPaths { get; } = new();
private readonly JsonSerializerOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public JsonContentSource(int priority)
{
Priority = priority;
@ -177,6 +245,11 @@ public sealed class JsonContentSource : IContentSource
_options.Converters.Add(new JsonStringEnumConverter());
}
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class
{
foreach (var path in DataPaths)
@ -232,6 +305,11 @@ public sealed class JsonContentSource : IContentSource
}
}
/// <summary>
/// 解析路径
/// </summary>
/// <param name="path">路径</param>
/// <returns>解析后的绝对路径</returns>
private string ResolvePath(string path)
{
if (path.StartsWith("res://") || path.StartsWith("user://"))
@ -242,6 +320,9 @@ public sealed class JsonContentSource : IContentSource
return path;
}
/// <summary>
/// 尝试反序列化列表
/// </summary>
private bool TryDeserializeList<T>(string json, out List<T> list) where T : class
{
try
@ -256,6 +337,9 @@ public sealed class JsonContentSource : IContentSource
}
}
/// <summary>
/// 尝试反序列化单个对象
/// </summary>
private bool TryDeserializeSingle<T>(string json, out T item) where T : class
{
try
@ -270,6 +354,9 @@ public sealed class JsonContentSource : IContentSource
}
}
/// <summary>
/// 尝试反序列化信封结构
/// </summary>
private bool TryDeserializeEnvelope<T>(string json, out JsonContentEnvelope<T> envelope) where T : class
{
try
@ -295,5 +382,4 @@ public sealed class JsonContentSource : IContentSource
public List<T> Items { get; set; }
public T Item { get; set; }
}
}
}

View File

@ -15,13 +15,26 @@ namespace Core;
/// </summary>
public interface IContentResource
{
/// <summary>
/// 获取定义类型
/// </summary>
/// <returns>类型</returns>
Type GetDefinitionType();
/// <summary>
/// 转换为定义对象
/// </summary>
/// <returns>定义对象</returns>
object ToDefinition();
}
/// <summary>
/// 资源集合接口
/// </summary>
public interface IContentResourceCollection
{
/// <summary>
/// 获取资源项集合
/// </summary>
/// <returns>资源项集合</returns>
IEnumerable<IContentResource> GetItems();
}
}

View File

@ -20,28 +20,81 @@ namespace Core;
public partial class DisciplineDefinitionResource : Resource, IContentResource
{
// --- Header ---
/// <summary>
/// 学科ID
/// </summary>
[Export] public string Id { get; set; }
/// <summary>
/// 名称键值
/// </summary>
[Export] public string NameKey { get; set; }
/// <summary>
/// 名称默认值
/// </summary>
[Export] public string NameFallback { get; set; }
/// <summary>
/// 描述键值
/// </summary>
[Export] public string DescriptionKey { get; set; }
/// <summary>
/// 描述默认值
/// </summary>
[Export] public string DescriptionFallback { get; set; }
/// <summary>
/// 图标路径
/// </summary>
[Export] public string IconPath { get; set; }
/// <summary>
/// 标签列表
/// </summary>
[Export] public Array<string> Tags { get; set; } = new();
// --- Buff ---
/// <summary>
/// Buff名称键值
/// </summary>
[Export] public string BuffNameKey { get; set; }
/// <summary>
/// Buff名称默认值
/// </summary>
[Export] public string BuffNameFallback { get; set; }
/// <summary>
/// Buff描述键值
/// </summary>
[Export] public string BuffDescriptionKey { get; set; }
/// <summary>
/// Buff描述默认值
/// </summary>
[Export] public string BuffDescriptionFallback { get; set; }
/// <summary>
/// Buff规则ID列表
/// </summary>
[Export] public Array<string> BuffRuleIds { get; set; } = new();
// --- Pools ---
/// <summary>
/// 角色池ID列表
/// </summary>
[Export] public Array<string> RolePoolIds { get; set; } = new();
/// <summary>
/// 物品池ID列表
/// </summary>
[Export] public Array<string> ItemPoolIds { get; set; } = new();
/// <summary>
/// 任务关键词ID列表
/// </summary>
[Export] public Array<string> TaskKeywordIds { get; set; } = new();
/// <summary>
/// 获取定义类型
/// </summary>
/// <returns>类型</returns>
public Type GetDefinitionType() => typeof(DisciplineDefinition);
/// <summary>
/// 转换为定义对象
/// </summary>
/// <returns>定义对象</returns>
public object ToDefinition()
{
var header = new DefinitionHeader
@ -98,6 +151,11 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
return definition;
}
/// <summary>
/// 添加范围
/// </summary>
/// <param name="source">源数组</param>
/// <param name="target">目标列表</param>
private static void AddRange(Array<string> source, List<string> target)
{
foreach (var value in source)
@ -105,5 +163,4 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
target.Add(value);
}
}
}
}

View File

@ -14,31 +14,57 @@ namespace Core;
/// </summary>
public readonly struct TaskCompletedEvent
{
/// <summary>
/// 完成的任务
/// </summary>
public TaskModel Task { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="task">任务</param>
public TaskCompletedEvent(TaskModel task)
{
Task = task;
}
}
/// <summary>
/// 任务失败事件
/// </summary>
public readonly struct TaskFailedEvent
{
/// <summary>
/// 失败的任务
/// </summary>
public TaskModel Task { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="task">任务</param>
public TaskFailedEvent(TaskModel task)
{
Task = task;
}
}
/// <summary>
/// 回合结束事件
/// </summary>
public readonly struct TurnEndedEvent
{
/// <summary>
/// 结束的回合数
/// </summary>
public int Turn { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="turn">回合数</param>
public TurnEndedEvent(int turn)
{
Turn = turn;
}
}
}

View File

@ -17,6 +17,11 @@ public sealed class DomainEventBus
{
private readonly Dictionary<Type, List<Delegate>> _handlers = new();
/// <summary>
/// 订阅事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="handler">事件处理器</param>
public void Subscribe<T>(Action<T> handler)
{
var type = typeof(T);
@ -29,6 +34,11 @@ public sealed class DomainEventBus
list.Add(handler);
}
/// <summary>
/// 取消订阅事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="handler">事件处理器</param>
public void Unsubscribe<T>(Action<T> handler)
{
var type = typeof(T);
@ -38,6 +48,11 @@ public sealed class DomainEventBus
}
}
/// <summary>
/// 发布事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="evt">事件对象</param>
public void Publish<T>(T evt)
{
var type = typeof(T);
@ -54,5 +69,4 @@ public sealed class DomainEventBus
}
}
}
}
}

View File

@ -16,28 +16,40 @@ public sealed class GameController : IController
{
private GameSession _session;
/// <summary>
/// 初始化控制器
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
}
/// <summary>
/// 开始执行阶段
/// </summary>
public void StartExecution()
{
if (_session.State.Turn.Phase != GamePhase.Planning) return;
_session.State.Turn.Phase = GamePhase.Execution;
}
/// <summary>
/// 结束执行阶段
/// </summary>
public void EndExecution()
{
if (_session.State.Turn.Phase != GamePhase.Execution) return;
_session.State.Turn.Phase = GamePhase.Review;
}
/// <summary>
/// 开始下一回合
/// </summary>
public void StartNextTurn()
{
if (_session.State.Turn.Phase != GamePhase.Review) return;
_session.State.Turn.CurrentTurn++;
_session.State.Turn.Phase = GamePhase.Planning;
}
}
}

View File

@ -14,12 +14,34 @@ namespace Core;
/// </summary>
public sealed class GameSession
{
/// <summary>
/// 游戏状态
/// </summary>
public GameState State { get; }
/// <summary>
/// 游戏内容数据库
/// </summary>
public GameContentDatabase Content { get; }
/// <summary>
/// 领域事件总线
/// </summary>
public DomainEventBus Events { get; }
/// <summary>
/// 本地化服务
/// </summary>
public ILocalizationService Localization { get; }
/// <summary>
/// 游戏系统集合
/// </summary>
public GameSystems Systems { get; }
/// <summary>
/// 构造游戏会话
/// </summary>
/// <param name="state">游戏状态</param>
/// <param name="content">内容数据库</param>
/// <param name="localization">本地化服务</param>
/// <param name="events">事件总线</param>
public GameSession(GameState state, GameContentDatabase content, ILocalizationService localization, DomainEventBus events)
{
State = state;
@ -30,6 +52,10 @@ public sealed class GameSession
Systems.Initialize(this);
}
/// <summary>
/// 创建默认游戏会话
/// </summary>
/// <returns>游戏会话实例</returns>
public static GameSession CreateDefault()
{
var registry = new ContentRegistry();
@ -50,9 +76,12 @@ public sealed class GameSession
return new GameSession(new GameState(), content, localization, events);
}
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
Systems.Tick(delta);
}
}
}

View File

@ -16,18 +16,48 @@ namespace Core;
/// </summary>
public interface IGameSystem
{
/// <summary>
/// 初始化系统
/// </summary>
/// <param name="session">游戏会话</param>
void Initialize(GameSession session);
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
void Tick(float delta);
}
/// <summary>
/// 游戏系统管理器
/// </summary>
public sealed class GameSystems
{
/// <summary>
/// 回合系统
/// </summary>
public TurnSystem Turn { get; } = new();
/// <summary>
/// 任务系统
/// </summary>
public TaskSystem Task { get; } = new();
/// <summary>
/// 经济系统
/// </summary>
public EconomySystem Economy { get; } = new();
/// <summary>
/// 羁绊/协同系统
/// </summary>
public SynergySystem Synergy { get; } = new();
/// <summary>
/// 分配系统
/// </summary>
public AssignmentSystem Assignment { get; } = new();
/// <summary>
/// 初始化所有系统
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
Turn.Initialize(session);
@ -37,6 +67,10 @@ public sealed class GameSystems
Assignment.Initialize(session);
}
/// <summary>
/// 更新所有系统
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
Turn.Tick(delta);
@ -47,32 +81,54 @@ public sealed class GameSystems
}
}
/// <summary>
/// 回合系统
/// </summary>
public sealed class TurnSystem : IGameSystem
{
private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
}
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
// 预留:回合推进计时器/阶段切换
}
}
/// <summary>
/// 任务系统
/// </summary>
public sealed class TaskSystem : IGameSystem
{
private GameSession _session;
private StatResolver _statResolver;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
_statResolver = new StatResolver(session);
}
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
if (_session.State.Turn.Phase != GamePhase.Execution)
@ -124,6 +180,10 @@ public sealed class TaskSystem : IGameSystem
}
}
/// <summary>
/// 推进任务进度
/// </summary>
/// <param name="delta">时间间隔</param>
private void AdvanceTasks(float delta)
{
var state = _session.State;
@ -157,6 +217,9 @@ public sealed class TaskSystem : IGameSystem
}
}
/// <summary>
/// 获取单位对任务的贡献
/// </summary>
private float GetUnitContribution(UnitEntry entry, TaskModel task, TaskDefinition taskDef, float delta)
{
var unit = entry.Unit;
@ -173,6 +236,9 @@ public sealed class TaskSystem : IGameSystem
return effectivePower;
}
/// <summary>
/// 获取任务基础效率
/// </summary>
private float GetTaskBasePower(UnitModel unit, TaskKind kind)
{
var academic = _statResolver.GetAttribute(unit, AttributeType.Academic);
@ -194,6 +260,9 @@ public sealed class TaskSystem : IGameSystem
};
}
/// <summary>
/// 获取状态乘数
/// </summary>
private float GetStatusMultiplier(UnitEntry entry)
{
var mood = entry.Unit.Statuses.Mood.Normalized;
@ -213,6 +282,9 @@ public sealed class TaskSystem : IGameSystem
return Math.Clamp(multiplier, 0.3f, 1.2f);
}
/// <summary>
/// 获取角色乘数
/// </summary>
private float GetRoleMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
@ -246,6 +318,9 @@ public sealed class TaskSystem : IGameSystem
return 1.0f;
}
/// <summary>
/// 获取需求乘数
/// </summary>
private float GetRequirementMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
@ -263,6 +338,9 @@ public sealed class TaskSystem : IGameSystem
return multiplier;
}
/// <summary>
/// 获取学科乘数
/// </summary>
private float GetDisciplineMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
@ -280,6 +358,9 @@ public sealed class TaskSystem : IGameSystem
return 0.7f;
}
/// <summary>
/// 获取难度系数
/// </summary>
private float GetDifficultyScale(TaskDifficulty difficulty)
{
return difficulty switch
@ -292,12 +373,18 @@ public sealed class TaskSystem : IGameSystem
};
}
/// <summary>
/// 获取任务定义
/// </summary>
private TaskDefinition GetTaskDefinition(TaskModel task)
{
if (string.IsNullOrWhiteSpace(task.DefinitionId)) return null;
return _session.Content.Tasks.TryGetValue(task.DefinitionId, out var definition) ? definition : null;
}
/// <summary>
/// 构建单位索引
/// </summary>
private Dictionary<Guid, UnitEntry> BuildUnitIndex()
{
var index = new Dictionary<Guid, UnitEntry>();
@ -324,6 +411,9 @@ public sealed class TaskSystem : IGameSystem
return index;
}
/// <summary>
/// 追踪贡献
/// </summary>
private void TrackContribution(UnitEntry entry, TaskModel task, float deltaContribution)
{
if (entry.Student == null) return;
@ -355,6 +445,9 @@ public sealed class TaskSystem : IGameSystem
}
}
/// <summary>
/// 经济系统
/// </summary>
public sealed class EconomySystem : IGameSystem
{
private GameSession _session;
@ -363,6 +456,10 @@ public sealed class EconomySystem : IGameSystem
private const int PostDocSalary = 1200;
private const int JuniorFacultySalary = 2000;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
@ -371,11 +468,18 @@ public sealed class EconomySystem : IGameSystem
_session.Events.Subscribe<TurnEndedEvent>(OnTurnEnded);
}
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
// 当前为回合驱动,不在 Tick 中结算
}
/// <summary>
/// 任务完成回调
/// </summary>
private void OnTaskCompleted(TaskCompletedEvent evt)
{
var task = evt.Task;
@ -404,6 +508,9 @@ public sealed class EconomySystem : IGameSystem
}
}
/// <summary>
/// 任务失败回调
/// </summary>
private void OnTaskFailed(TaskFailedEvent evt)
{
var task = evt.Task;
@ -412,12 +519,18 @@ public sealed class EconomySystem : IGameSystem
_session.State.Economy.Reputation -= penalty;
}
/// <summary>
/// 回合结束回调
/// </summary>
private void OnTurnEnded(TurnEndedEvent evt)
{
ApplySalaries();
ApplyInterest();
}
/// <summary>
/// 支付薪水
/// </summary>
private void ApplySalaries()
{
var economy = _session.State.Economy;
@ -436,6 +549,9 @@ public sealed class EconomySystem : IGameSystem
}
}
/// <summary>
/// 计算利息
/// </summary>
private void ApplyInterest()
{
var economy = _session.State.Economy;
@ -446,6 +562,9 @@ public sealed class EconomySystem : IGameSystem
economy.Money += interest;
}
/// <summary>
/// 更新利率
/// </summary>
private void UpdateInterestRate()
{
var economy = _session.State.Economy;
@ -456,6 +575,9 @@ public sealed class EconomySystem : IGameSystem
}
}
/// <summary>
/// 添加论文
/// </summary>
private void AddPaper(PaperRank rank)
{
var inventory = _session.State.Inventory;
@ -467,6 +589,9 @@ public sealed class EconomySystem : IGameSystem
inventory.PaperCounts[rank] += 1;
}
/// <summary>
/// 添加物品
/// </summary>
private void AddItem(string itemId, int count)
{
if (string.IsNullOrWhiteSpace(itemId)) return;
@ -479,6 +604,9 @@ public sealed class EconomySystem : IGameSystem
inventory.ItemCounts[itemId] += count;
}
/// <summary>
/// 根据难度获取论文等级
/// </summary>
private PaperRank GetPaperRankByDifficulty(TaskDifficulty difficulty)
{
return difficulty switch
@ -492,20 +620,34 @@ public sealed class EconomySystem : IGameSystem
}
}
/// <summary>
/// 羁绊/协同系统
/// </summary>
public sealed class SynergySystem : IGameSystem
{
private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
}
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
RecalculateSynergy();
}
/// <summary>
/// 重新计算协同
/// </summary>
private void RecalculateSynergy()
{
var state = _session.State.Synergy;
@ -518,6 +660,9 @@ public sealed class SynergySystem : IGameSystem
ApplySynergyDefinitions(state);
}
/// <summary>
/// 统计单位标签
/// </summary>
private void CountUnitTags(SynergyState synergy)
{
var roster = _session.State.Roster;
@ -534,6 +679,9 @@ public sealed class SynergySystem : IGameSystem
}
}
/// <summary>
/// 添加单位标签
/// </summary>
private void AddUnitTags(SynergyState synergy, UnitModel unit)
{
if (unit == null) return;
@ -548,6 +696,9 @@ public sealed class SynergySystem : IGameSystem
}
}
/// <summary>
/// 增加堆叠计数
/// </summary>
private void AddStack(Dictionary<string, int> stacks, string id)
{
if (string.IsNullOrWhiteSpace(id)) return;
@ -559,6 +710,9 @@ public sealed class SynergySystem : IGameSystem
stacks[id] += 1;
}
/// <summary>
/// 应用协同定义
/// </summary>
private void ApplySynergyDefinitions(SynergyState synergy)
{
foreach (var archetype in _session.Content.Archetypes.Values)
@ -572,6 +726,9 @@ public sealed class SynergySystem : IGameSystem
}
}
/// <summary>
/// 应用协同层级
/// </summary>
private void ApplySynergyTier(string id, List<SynergyTier> tiers, Dictionary<string, int> stacks, SynergyState synergy)
{
if (string.IsNullOrWhiteSpace(id)) return;
@ -586,6 +743,9 @@ public sealed class SynergySystem : IGameSystem
}
}
/// <summary>
/// 合并修正
/// </summary>
private void MergeModifiers(ModifierBundle target, ModifierBundle source)
{
if (target == null || source == null) return;
@ -595,6 +755,9 @@ public sealed class SynergySystem : IGameSystem
target.RuleIds.AddRange(source.RuleIds);
}
/// <summary>
/// 清除修正
/// </summary>
private void ClearModifiers(ModifierBundle bundle)
{
bundle.AttributeModifiers.Clear();
@ -604,18 +767,28 @@ public sealed class SynergySystem : IGameSystem
}
}
/// <summary>
/// 分配系统
/// </summary>
public sealed class AssignmentSystem : IGameSystem
{
private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session)
{
_session = session;
}
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta)
{
// 预留:人员分配、交接惩罚等
}
}
}

View File

@ -15,18 +15,43 @@ namespace Core;
/// </summary>
public interface ILocalizationService
{
/// <summary>
/// 翻译本地化文本对象
/// </summary>
/// <param name="text">文本对象</param>
/// <returns>翻译后的字符串</returns>
string Translate(LocalizedText text);
/// <summary>
/// 翻译键值
/// </summary>
/// <param name="key">键值</param>
/// <param name="fallback">默认值</param>
/// <returns>翻译后的字符串</returns>
string Translate(string key, string fallback = null);
}
/// <summary>
/// Godot 本地化服务实现
/// </summary>
public sealed class GodotLocalizationService : ILocalizationService
{
/// <summary>
/// 翻译本地化文本对象
/// </summary>
/// <param name="text">文本对象</param>
/// <returns>翻译后的字符串</returns>
public string Translate(LocalizedText text)
{
if (text == null) return string.Empty;
return Translate(text.Key, text.Fallback);
}
/// <summary>
/// 翻译键值
/// </summary>
/// <param name="key">键值</param>
/// <param name="fallback">默认值</param>
/// <returns>翻译后的字符串</returns>
public string Translate(string key, string fallback = null)
{
if (string.IsNullOrWhiteSpace(key))
@ -42,5 +67,4 @@ public sealed class GodotLocalizationService : ILocalizationService
return translated;
}
}
}

View File

@ -14,16 +14,39 @@ namespace Core;
/// </summary>
public sealed class ModManifest
{
/// <summary>
/// Mod ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// Mod 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 版本
/// </summary>
public string Version { get; set; }
/// <summary>
/// 依赖列表
/// </summary>
public List<string> Dependencies { get; } = new();
/// <summary>
/// 内容路径列表
/// </summary>
public List<string> ContentPaths { get; } = new();
}
/// <summary>
/// Mod 包
/// </summary>
public sealed class ModPackage
{
/// <summary>
/// 清单
/// </summary>
public ModManifest Manifest { get; set; } = new();
/// <summary>
/// 根路径
/// </summary>
public string RootPath { get; set; }
}
}

View File

@ -14,26 +14,50 @@ namespace Core;
/// </summary>
public interface IView<TModel>
{
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">数据模型</param>
void Bind(TModel model);
}
/// <summary>
/// 控制器接口
/// </summary>
public interface IController
{
/// <summary>
/// 初始化控制器
/// </summary>
/// <param name="session">游戏会话</param>
void Initialize(GameSession session);
}
/// <summary>
/// 模型视图基类
/// </summary>
/// <typeparam name="TModel">模型类型</typeparam>
public abstract partial class ModelView<TModel> : Node, IView<TModel>
{
/// <summary>
/// 数据模型
/// </summary>
public TModel Model { get; private set; }
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">数据模型</param>
public virtual void Bind(TModel model)
{
Model = model;
OnModelBound();
}
/// <summary>
/// 当模型绑定时调用
/// </summary>
protected virtual void OnModelBound()
{
}
}
}

View File

@ -17,11 +17,21 @@ public sealed class StatResolver
{
private readonly GameSession _session;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="session">游戏会话</param>
public StatResolver(GameSession session)
{
_session = session;
}
/// <summary>
/// 获取计算后的属性值
/// </summary>
/// <param name="unit">单位</param>
/// <param name="type">属性类型</param>
/// <returns>属性值</returns>
public float GetAttribute(UnitModel unit, AttributeType type)
{
var value = GetBaseAttribute(unit, type);
@ -32,6 +42,12 @@ public sealed class StatResolver
return value;
}
/// <summary>
/// 获取基础属性值
/// </summary>
/// <param name="unit">单位</param>
/// <param name="type">属性类型</param>
/// <returns>基础值</returns>
private float GetBaseAttribute(UnitModel unit, AttributeType type)
{
return type switch
@ -46,6 +62,9 @@ public sealed class StatResolver
};
}
/// <summary>
/// 应用学科加成
/// </summary>
private void ApplyDiscipline(string disciplineId, AttributeType type, ref float value)
{
if (string.IsNullOrWhiteSpace(disciplineId)) return;
@ -53,6 +72,9 @@ public sealed class StatResolver
ApplyBundle(discipline.Buff?.Modifiers, type, ref value);
}
/// <summary>
/// 应用特质加成
/// </summary>
private void ApplyTraits(List<string> traitIds, AttributeType type, ref float value)
{
if (traitIds == null || traitIds.Count == 0) return;
@ -65,6 +87,9 @@ public sealed class StatResolver
}
}
/// <summary>
/// 应用装备加成
/// </summary>
private void ApplyItems(List<string> itemIds, AttributeType type, ref float value)
{
if (itemIds == null || itemIds.Count == 0) return;
@ -77,6 +102,9 @@ public sealed class StatResolver
}
}
/// <summary>
/// 应用修正包
/// </summary>
private void ApplyBundle(ModifierBundle bundle, AttributeType type, ref float value)
{
if (bundle == null) return;
@ -86,5 +114,4 @@ public sealed class StatResolver
value = (value + modifier.Add) * modifier.Multiplier;
}
}
}
}

View File

@ -4,14 +4,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml;
/// <summary>
/// 办公格子/图块对象
/// </summary>
public partial class Cube : StaticBody2D, ITileDraggable
{
// 定义各种家具的占用区域
private static readonly Rect2I tableRect = new(0, 0, 3, 2);
private static readonly Rect2I table2Rect = new(0, -1, 3, 1);
private static readonly Rect2I chairRect = new(1, 1, 1, 2);
private static readonly Rect2I chair2Rect = new(1, -2, 1, 2);
private static readonly Rect2I equipRect = new(0, 0, 3, 2);
private static readonly Rect2I equip2Rect = new(0, -1, 3, 1);
// 定义桌子的主题映射
private static readonly TileMapping[] tableThemes = {
new(new Vector2I(1, 30), tableRect),
new(new Vector2I(4, 30), tableRect),
@ -19,6 +25,7 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(7, 28), tableRect),
new(new Vector2I(10, 28), tableRect),
};
// 定义第二种桌子的主题映射
private static readonly TileMapping[] table2Themes = {
new(new Vector2I(1, 31), table2Rect),
new(new Vector2I(4, 31), table2Rect),
@ -26,12 +33,14 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(7, 29), table2Rect),
new(new Vector2I(10, 29), table2Rect),
};
// 定义椅子的主题映射
private static readonly TileMapping[] chairThemes = {
new(new Vector2I(-1, 7), chairRect),
new(new Vector2I(0, 7), chairRect),
new(new Vector2I(-1, 9), chairRect),
new(new Vector2I(0, 9), chairRect),
};
// 定义第二种椅子的主题映射
private static readonly TileMapping[] chair2Themes = {
new(new Vector2I(-1, 10), chair2Rect),
new(new Vector2I(0, 10), chair2Rect),
@ -39,12 +48,13 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(0, 12), chair2Rect),
};
// 定义设备的主题映射
private static readonly TileMapping[] equipThemes = {
// one laptop
// 一台笔记本电脑
new(new Vector2I(13, 26), equipRect),
// one laptop with one monitor
// 一台笔记本电脑加一台显示器
new(new Vector2I(7, 26), equipRect),
// one desktop PC
// 一台台式机
new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, 0)] = new Vector2I(11, 17),
[new Vector2I(1, 0)] = new Vector2I(8, 32),
@ -53,30 +63,34 @@ public partial class Cube : StaticBody2D, ITileDraggable
[new Vector2I(1, 1)] = new Vector2I(8, 33),
[new Vector2I(2, 1)] = new Vector2I(9, 33),
}),
// one desktop PC with one monitors
// 一台台式机加一台显示器
new(new Vector2I(7, 32), equipRect),
};
// 定义第二种设备的主题映射
private static readonly TileMapping[] equip2Themes = {
// one laptop
// 一台笔记本电脑
new(new Vector2I(13, 31), equip2Rect),
// one laptop with one monitor
// 一台笔记本电脑加一台显示器
new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, -1)] = new Vector2I(13, 32),
[new Vector2I(1, -1)] = new Vector2I(10, 7),
[new Vector2I(2, -1)] = new Vector2I(15, 32),
}),
// one desktop PC
// 一台台式机
new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, -1)] = new Vector2I(13, 32),
[new Vector2I(1, -1)] = new Vector2I(14, 32),
[new Vector2I(2, -1)] = new Vector2I(15, 30),
}),
// one desktop PC with one monitors
// 一台台式机加一台显示器
new(new Vector2I(13, 33), equip2Rect),
};
private bool _draggable;
/// <summary>
/// 是否可拖拽
/// </summary>
public bool Draggable {
get => _draggable;
set {
@ -86,10 +100,20 @@ public partial class Cube : StaticBody2D, ITileDraggable
}
private readonly Guid _id = Guid.NewGuid();
/// <summary>
/// 唯一标识符
/// </summary>
public Guid Id => _id;
/// <summary>
/// 在网格中的位置
/// </summary>
public Vector2I TilePosition { get; set; } = new Vector2I(5,5);
private bool _isCollided = true;
/// <summary>
/// 是否处于碰撞状态(显示红色背景)
/// </summary>
public bool IsCollided {
get => _isCollided;
set {
@ -103,9 +127,15 @@ public partial class Cube : StaticBody2D, ITileDraggable
}
}
private static readonly Rect2I tileRect = new(-1, -2, 4, 5);
/// <summary>
/// 占用矩形区域
/// </summary>
public Rect2I TileRect => tileRect;
private Vector2I _mouseOffset;
/// <summary>
/// 鼠标拖拽偏移
/// </summary>
public Vector2I MouseOffset => _mouseOffset;
private static readonly ITileDraggable.SpecialTile[] specialTiles = {
@ -114,15 +144,27 @@ public partial class Cube : StaticBody2D, ITileDraggable
};
/// <summary>
/// 特殊图块(如座位)
/// </summary>
public ITileDraggable.SpecialTile[] SpecialTiles => specialTiles;
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 节点进入场景树时调用
/// </summary>
public override void _Ready()
{
RandomChangeTheme();
IsCollided = false;
}
/// <summary>
/// 处理输入事件
/// </summary>
/// <param name="viewport">视口</param>
/// <param name="event">输入事件</param>
/// <param name="shapeIdx">形状索引</param>
public override void _InputEvent(Viewport viewport, InputEvent @event, int shapeIdx)
{
if (@event.IsActionPressed("mouse_left_press")) {
@ -142,6 +184,10 @@ public partial class Cube : StaticBody2D, ITileDraggable
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧处理
/// </summary>
/// <param name="delta">时间间隔</param>
public override void _Process(double delta)
{
GlobalPosition = TilePosition * 48;
@ -153,6 +199,10 @@ public partial class Cube : StaticBody2D, ITileDraggable
private int _chair2ThemeIdx;
private int _equipThemeIdx;
private int _equip2ThemeIdx;
/// <summary>
/// 随机更换主题样式
/// </summary>
public void RandomChangeTheme() {
_tableThemeIdx = GD.RandRange(0, tableThemes.Length-1);
_chairThemeIdx = GD.RandRange(0, chairThemes.Length-1);
@ -177,6 +227,12 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(1,1),
new(2,1),
});
/// <summary>
/// 获取指定位置的图块类型
/// </summary>
/// <param name="pos">相对位置</param>
/// <returns>图块类型</returns>
public Lab.MapNodeType GetTileType(Vector2I pos)
{
GD.Print($"query position of {pos}");
@ -189,6 +245,11 @@ public partial class Cube : StaticBody2D, ITileDraggable
return Lab.MapNodeType.Walkable;
}
/// <summary>
/// 设置椅子朝向(待实现)
/// </summary>
/// <param name="target">目标位置</param>
/// <param name="idx">索引</param>
public void ChairFaceTo(Vector2I target, int idx) {
if (idx == 0) {
var theme = chairThemes[_chairThemeIdx];
@ -196,4 +257,4 @@ public partial class Cube : StaticBody2D, ITileDraggable
}
}
}
}

View File

@ -17,41 +17,105 @@ using Core;
public partial class GameManager : Node
{
/// <summary>
/// Indicates if the game is currently in tutorial mode.
/// 指示当前是否处于教程模式
/// </summary>
public static bool IsTutorial { get; private set; }
/// <summary>
/// 下一个场景的路径
/// </summary>
public static string NextScene { get; set; } = null;
/// <summary>
/// 双倍大小的箭头光标资源
/// </summary>
public static readonly Resource Arrow2X = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png");
/// <summary>
/// 最大回合数
/// </summary>
[Export]
public int MaxTurns = 30;
// --- Global State ---
/// <summary>
/// 游戏会话实例
/// </summary>
public GameSession Session { get; private set; }
/// <summary>
/// 当前游戏状态
/// </summary>
public GameState State => Session?.State;
/// <summary>
/// 游戏控制器
/// </summary>
public GameController Controller { get; private set; }
/// <summary>
/// 当前游戏阶段
/// </summary>
public GamePhase CurrentPhase => State?.Turn.Phase ?? GamePhase.Planning;
/// <summary>
/// 当前回合数
/// </summary>
public int CurrentTurn => State?.Turn.CurrentTurn ?? 1;
// --- Domain Model ---
/// <summary>
/// 导师模型
/// </summary>
public MentorModel Mentor => State?.Roster.Mentor;
/// <summary>
/// 学生列表
/// </summary>
public List<StudentModel> Students => State?.Roster.Students;
/// <summary>
/// 当前活动任务列表
/// </summary>
public List<TaskModel> ActiveTasks => State?.Tasks.ActiveTasks;
/// <summary>
/// 经济状态
/// </summary>
public EconomyState Economy => State?.Economy;
// --- Signals ---
/// <summary>
/// 阶段变更信号
/// </summary>
/// <param name="phase">阶段枚举的整数值</param>
[Signal] public delegate void PhaseChangedEventHandler(int phase); // int cast of GamePhase
/// <summary>
/// 回合变更信号
/// </summary>
/// <param name="turn">当前回合数</param>
[Signal] public delegate void TurnChangedEventHandler(int turn);
// Singleton instance access (if needed, though Godot uses node paths)
/// <summary>
/// 单例实例
/// </summary>
public static GameManager Instance { get; private set; }
/// <summary>
/// 进入场景树时调用
/// </summary>
public override void _EnterTree()
{
Instance = this;
}
/// <summary>
/// 准备就绪时调用
/// </summary>
public override void _Ready()
{
Input.SetCustomMouseCursor(Arrow2X);
@ -60,6 +124,9 @@ public partial class GameManager : Node
InitializeGame();
}
/// <summary>
/// 初始化游戏数据
/// </summary>
private void InitializeGame()
{
Session = GameSession.CreateDefault();
@ -83,6 +150,10 @@ public partial class GameManager : Node
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧调用
/// </summary>
/// <param name="delta">距离上一帧的时间间隔</param>
public override void _Process(double delta)
{
if (CurrentPhase == GamePhase.Execution)
@ -157,4 +228,4 @@ public partial class GameManager : Node
GD.Print($"Turn {CurrentTurn} Started. Phase: Planning");
}
}
}

View File

@ -1,6 +1,15 @@
using Godot;
/// <summary>
/// 辅助工具类
/// </summary>
public class H {
/// <summary>
/// 判断矩形是否包含点(包含边界)
/// </summary>
/// <param name="r">矩形区域</param>
/// <param name="p">点坐标</param>
/// <returns>如果点在矩形内包括边界则返回true</returns>
public static bool RectHasPointInclusive(Rect2I r, Vector2I p) {
if (r.HasPoint(p)) {
return true;
@ -13,4 +22,4 @@ public class H {
}
return false;
}
}
}

View File

@ -3,25 +3,73 @@ using System.Diagnostics.SymbolStore;
using System.Dynamic;
using Godot;
/// <summary>
/// 可拖拽图块接口
/// </summary>
public interface ITileDraggable {
/// <summary>
/// 图块在网格中的位置
/// </summary>
Vector2I TilePosition { get; set; }
/// <summary>
/// 是否允许拖拽
/// </summary>
bool Draggable { get; set; }
/// <summary>
/// 是否处于碰撞状态(例如位置无效或重叠)
/// </summary>
bool IsCollided { get; set; }
/// <summary>
/// 图块占据的矩形区域(相对于 TilePosition
/// </summary>
Rect2I TileRect { get; }
/// <summary>
/// 拖拽时鼠标相对于图块原点的偏移量
/// </summary>
Vector2I MouseOffset { get; }
/// <summary>
/// 特殊功能图块列表(如座位、交互点等)
/// </summary>
SpecialTile[] SpecialTiles { get; }
/// <summary>
/// 特殊图块定义
/// </summary>
class SpecialTile {
/// <summary>
/// 相对位置
/// </summary>
public readonly Vector2I Position;
/// <summary>
/// 节点类型(如可行走、作为座位等)
/// </summary>
public readonly Lab.MapNodeType NodeType;
/// <summary>
/// 构造特殊图块
/// </summary>
/// <param name="pos">相对位置</param>
/// <param name="node">节点类型</param>
public SpecialTile(Vector2I pos, Lab.MapNodeType node) {
Position = pos;
NodeType = node;
}
}
/// <summary>
/// 获取指定相对坐标处的图块类型
/// </summary>
/// <param name="pos">相对坐标</param>
/// <returns>地图节点类型</returns>
Lab.MapNodeType GetTileType(Vector2I pos);
/// <summary>
/// 唯一标识符
/// </summary>
Guid Id { get; }
}
}

View File

@ -3,18 +3,26 @@ using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 实验室场景主控脚本
/// </summary>
public partial class Lab : Node2D
{
/// <summary>
/// 地图节点类型标志位
/// </summary>
[Flags]
public enum MapNodeType
{
Invalid = 0,
Walkable = 1,
Wall = 2,
Blocker = 4,
SeatUp = 8,
SeatDown = 16,
Invalid = 0, // 无效
Walkable = 1, // 可行走
Wall = 2, // 墙壁
Blocker = 4, // 阻挡物
SeatUp = 8, // 上方座位
SeatDown = 16, // 下方座位
}
// 墙壁矩形区域列表
private static readonly Rect2I[] wallRectangles = {
new(0,0,40,2),
new(0,5,1, 15),
@ -23,9 +31,15 @@ public partial class Lab : Node2D
};
private readonly Dictionary<Guid, ITileDraggable> _furnitureIDs = new();
/// <summary>
/// 家具字典通过ID索引
/// </summary>
public Dictionary<Guid, ITileDraggable> Furniture => _furnitureIDs;
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker");
@ -57,6 +71,10 @@ public partial class Lab : Node2D
private Label _moneyLabel;
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
switch(GD.RandRange(0,2)) {
@ -82,12 +100,21 @@ public partial class Lab : Node2D
}
private bool _isDragging;
/// <summary>
/// 拾起物品
/// </summary>
/// <param name="target">目标物品</param>
public void Pickup(ITileDraggable target) {
if (target == null) return;
_isDragging = true;
DraggingTarget = target;
}
/// <summary>
/// 放下物品
/// </summary>
/// <param name="target">目标物品</param>
public void PutDown(ITileDraggable target) {
if (target == null) return;
_isDragging = false;
@ -98,6 +125,10 @@ public partial class Lab : Node2D
DraggingTarget = null;
UpdateMap();
}
/// <summary>
/// 当前正在拖拽的目标
/// </summary>
public ITileDraggable DraggingTarget { get; set; }
public const int MapWidth = 40;
@ -109,6 +140,9 @@ public partial class Lab : Node2D
private TileMapLayer _tileMap;
/// <summary>
/// 更新地图数据
/// </summary>
public void UpdateMap()
{
for (int i = 0; i < MapWidth; i++) {
@ -132,8 +166,6 @@ public partial class Lab : Node2D
}
}
private static bool IsValidPosition(Vector2I pos)
{
int x = pos.X;
@ -143,6 +175,7 @@ public partial class Lab : Node2D
}
return true;
}
private List<Vector2I> GetNeighbors(Vector2I pos) {
int x = pos.X;
int y = pos.Y;
@ -166,6 +199,12 @@ public partial class Lab : Node2D
return neighbor;
}
/// <summary>
/// 获取最短路径
/// </summary>
/// <param name="start">起点</param>
/// <param name="end">终点</param>
/// <returns>路径点列表</returns>
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
{
for (int j = 0; j < MapHeight; j++) {
@ -258,19 +297,35 @@ public partial class Lab : Node2D
}
}
/// <summary>
/// 获取指定位置的地图节点类型
/// </summary>
/// <param name="pos">位置</param>
/// <returns>节点类型</returns>
public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) {
if (!IsValidPosition(pos)) return MapNodeType.Invalid;
return _blocks[pos.X, pos.Y];
}
/// <summary>
/// 世界坐标转网格坐标
/// </summary>
/// <param name="pos">世界坐标</param>
/// <returns>网格坐标</returns>
public Vector2I Point2Coord(Vector2 pos) {
return _tileMap.LocalToMap(_tileMap.ToLocal(pos));
}
/// <summary>
/// 获取家具的特殊位置(如座位)
/// </summary>
/// <param name="fId">家具ID</param>
/// <param name="idx">索引</param>
/// <returns>特殊位置坐标</returns>
public Vector2I GetFurSpecialPosition(Guid fId, int idx) {
if (!_furnitureIDs.ContainsKey(fId)) return Vector2I.Zero;
if (idx < 0 || idx > _furnitureIDs[fId].SpecialTiles.Length) return Vector2I.Zero;
return _furnitureIDs[fId].SpecialTiles[idx].Position + _furnitureIDs[fId].TilePosition;
}
}
}

View File

@ -2,6 +2,9 @@ using Godot;
using System;
using System.Collections.Generic;
/// <summary>
/// 场景加载器
/// </summary>
public partial class Loader : Control
{
private ProgressBar _progressBar;
@ -12,6 +15,9 @@ public partial class Loader : Control
};
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
_progressBar = GetNode<ProgressBar>("ProgressBar");
@ -31,6 +37,10 @@ public partial class Loader : Control
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
Godot.Collections.Array progress = new();
@ -53,4 +63,4 @@ public partial class Loader : Control
break;
}
}
}
}

View File

@ -1,15 +1,25 @@
using Godot;
using System;
/// <summary>
/// Logo场景
/// </summary>
public partial class LogoScene : Node2D
{
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() {
GetNode<AnimationPlayer>("AnimationPlayer").AnimationFinished += OnAnimationPlayerAnimationFinished;
}
/// <summary>
/// 动画播放结束回调
/// </summary>
/// <param name="animationName">动画名称</param>
public void OnAnimationPlayerAnimationFinished(StringName animationName)
{
GD.Print("FFF");
GetTree().ChangeSceneToFile("res://scenes/loader.tscn");
}
}
}

View File

@ -12,44 +12,142 @@ namespace Models;
/// </summary>
public static class CoreIds
{
/// <summary>
/// 默认命名空间
/// </summary>
public const string Namespace = "core";
// Disciplines
/// <summary>
/// 生物学
/// </summary>
public const string DisciplineBiology = "core:discipline_biology";
/// <summary>
/// 化学
/// </summary>
public const string DisciplineChemistry = "core:discipline_chemistry";
/// <summary>
/// 环境科学
/// </summary>
public const string DisciplineEnvironment = "core:discipline_environment";
/// <summary>
/// 材料学
/// </summary>
public const string DisciplineMaterials = "core:discipline_materials";
/// <summary>
/// 医学
/// </summary>
public const string DisciplineMedicine = "core:discipline_medicine";
/// <summary>
/// 计算机
/// </summary>
public const string DisciplineComputer = "core:discipline_computer";
/// <summary>
/// 数学
/// </summary>
public const string DisciplineMath = "core:discipline_math";
/// <summary>
/// 物理学
/// </summary>
public const string DisciplinePhysics = "core:discipline_physics";
/// <summary>
/// 机械工程
/// </summary>
public const string DisciplineMechanical = "core:discipline_mechanical";
/// <summary>
/// 哲学
/// </summary>
public const string DisciplinePhilosophy = "core:discipline_philosophy";
/// <summary>
/// 经济学
/// </summary>
public const string DisciplineEconomics = "core:discipline_economics";
/// <summary>
/// 法学
/// </summary>
public const string DisciplineLaw = "core:discipline_law";
/// <summary>
/// 文学
/// </summary>
public const string DisciplineLiterature = "core:discipline_literature";
/// <summary>
/// 农学
/// </summary>
public const string DisciplineAgriculture = "core:discipline_agriculture";
/// <summary>
/// 管理学
/// </summary>
public const string DisciplineManagement = "core:discipline_management";
/// <summary>
/// 艺术
/// </summary>
public const string DisciplineArt = "core:discipline_art";
// Archetypes
/// <summary>
/// 卷王
/// </summary>
public const string ArchetypeGrinder = "core:archetype_grinder";
/// <summary>
/// 摸鱼
/// </summary>
public const string ArchetypeSlacker = "core:archetype_slacker";
/// <summary>
/// 精英
/// </summary>
public const string ArchetypeElite = "core:archetype_elite";
/// <summary>
/// 天才
/// </summary>
public const string ArchetypeProdigy = "core:archetype_prodigy";
/// <summary>
/// 吉祥物
/// </summary>
public const string ArchetypeMascot = "core:archetype_mascot";
// Roles
/// <summary>
/// 码农
/// </summary>
public const string RoleCoder = "core:role_coder";
/// <summary>
/// 写手
/// </summary>
public const string RoleWriter = "core:role_writer";
/// <summary>
/// 实验员
/// </summary>
public const string RoleLabRat = "core:role_lab_rat";
/// <summary>
/// 讲演者
/// </summary>
public const string RolePresenter = "core:role_presenter";
/// <summary>
/// 记录员
/// </summary>
public const string RoleScribe = "core:role_scribe";
/// <summary>
/// 辩论者
/// </summary>
public const string RoleOrator = "core:role_orator";
/// <summary>
/// 管家
/// </summary>
public const string RoleSteward = "core:role_steward";
/// <summary>
/// 炼金术士
/// </summary>
public const string RoleAlchemist = "core:role_alchemist";
/// <summary>
/// 极客
/// </summary>
public const string RoleGeek = "core:role_geek";
/// <summary>
/// 勘测员
/// </summary>
public const string RoleSurveyor = "core:role_surveyor";
/// <summary>
/// 思考者
/// </summary>
public const string RoleThinker = "core:role_thinker";
}
}

View File

@ -17,16 +17,36 @@ namespace Models;
/// </summary>
public sealed class LocalizedText
{
/// <summary>
/// 键值
/// </summary>
public string Key { get; set; }
/// <summary>
/// 默认值
/// </summary>
public string Fallback { get; set; }
}
public sealed class DefinitionHeader
{
/// <summary>
/// 定义ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public LocalizedText Name { get; set; } = new();
/// <summary>
/// 描述
/// </summary>
public LocalizedText Description { get; set; } = new();
/// <summary>
/// 图标路径
/// </summary>
public string IconPath { get; set; }
/// <summary>
/// 标签列表
/// </summary>
public List<string> Tags { get; } = new();
}
}

View File

@ -14,18 +14,44 @@ namespace Models;
/// </summary>
public sealed class DisciplineDefinition
{
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 学科Buff
/// </summary>
public DisciplineBuff Buff { get; set; } = new();
/// <summary>
/// 角色池ID列表
/// </summary>
public List<string> RolePoolIds { get; } = new();
/// <summary>
/// 物品池ID列表
/// </summary>
public List<string> ItemPoolIds { get; } = new();
/// <summary>
/// 任务关键词ID列表
/// </summary>
public List<string> TaskKeywordIds { get; } = new();
}
/// <summary>
/// 学科Buff定义
/// </summary>
public sealed class DisciplineBuff
{
/// <summary>
/// 名称
/// </summary>
public LocalizedText Name { get; set; } = new();
/// <summary>
/// 描述
/// </summary>
public LocalizedText Description { get; set; } = new();
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new();
}
}

View File

@ -12,32 +12,31 @@ namespace Models;
/// </summary>
public enum AttributeType
{
Academic,
Engineering,
Writing,
Financial,
Social,
Activation
Academic, // 学术
Engineering, // 工程
Writing, // 写作
Financial, // 财务
Social, // 社交
Activation // 活跃/执行
}
public enum ResourceType
{
Money,
Reputation,
ResearchPoints,
Paper,
Inspiration,
Time
Money, // 金钱
Reputation, // 声望
ResearchPoints, // 科研点
Paper, // 论文
Inspiration, // 灵感
Time // 时间
}
public enum StatusType
{
Stress,
Sanity,
Mood,
Stamina,
Loyalty,
Energy,
Health
}
Stress, // 压力
Sanity, // 理智
Mood, // 心情
Stamina, // 体力
Loyalty, // 忠诚度
Energy, // 精力
Health // 健康
}

View File

@ -14,13 +14,36 @@ namespace Models;
/// </summary>
public sealed class GameContentDatabase
{
/// <summary>
/// 学科定义字典
/// </summary>
public Dictionary<string, DisciplineDefinition> Disciplines { get; } = new();
/// <summary>
/// 原型定义字典
/// </summary>
public Dictionary<string, ArchetypeDefinition> Archetypes { get; } = new();
/// <summary>
/// 角色定义字典
/// </summary>
public Dictionary<string, RoleDefinition> Roles { get; } = new();
/// <summary>
/// 特质定义字典
/// </summary>
public Dictionary<string, TraitDefinition> Traits { get; } = new();
/// <summary>
/// 任务定义字典
/// </summary>
public Dictionary<string, TaskDefinition> Tasks { get; } = new();
/// <summary>
/// 物品定义字典
/// </summary>
public Dictionary<string, ItemDefinition> Items { get; } = new();
/// <summary>
/// 论文定义字典
/// </summary>
public Dictionary<string, PaperDefinition> Papers { get; } = new();
/// <summary>
/// 肉鸽天赋定义字典
/// </summary>
public Dictionary<string, RoguelitePerkDefinition> RoguelitePerks { get; } = new();
}
}

View File

@ -17,69 +17,179 @@ namespace Models;
/// </summary>
public sealed class GameState
{
/// <summary>
/// 回合状态
/// </summary>
public TurnState Turn { get; } = new();
/// <summary>
/// 经济状态
/// </summary>
public EconomyState Economy { get; } = new();
/// <summary>
/// 人员状态
/// </summary>
public RosterState Roster { get; } = new();
/// <summary>
/// 任务状态
/// </summary>
public TaskState Tasks { get; } = new();
/// <summary>
/// 库存状态
/// </summary>
public InventoryState Inventory { get; } = new();
/// <summary>
/// 协同状态
/// </summary>
public SynergyState Synergy { get; } = new();
/// <summary>
/// 肉鸽状态
/// </summary>
public RogueliteState Roguelite { get; } = new();
}
/// <summary>
/// 游戏阶段
/// </summary>
public enum GamePhase
{
Planning,
Execution,
Review
Planning, // 筹备阶段
Execution, // 执行阶段
Review // 结算阶段
}
/// <summary>
/// 回合状态数据
/// </summary>
public sealed class TurnState
{
/// <summary>
/// 当前回合
/// </summary>
public int CurrentTurn { get; set; } = 1;
/// <summary>
/// 最大回合
/// </summary>
public int MaxTurns { get; set; } = 30;
/// <summary>
/// 当前阶段
/// </summary>
public GamePhase Phase { get; set; } = GamePhase.Planning;
}
/// <summary>
/// 经济状态数据
/// </summary>
public sealed class EconomyState
{
/// <summary>
/// 资金
/// </summary>
public int Money { get; set; } = 50000;
/// <summary>
/// 声望
/// </summary>
public int Reputation { get; set; }
/// <summary>
/// 科研点数
/// </summary>
public int ResearchPoints { get; set; }
/// <summary>
/// 利率
/// </summary>
public float InterestRate { get; set; } = 0.0f;
}
/// <summary>
/// 人员状态数据
/// </summary>
public sealed class RosterState
{
/// <summary>
/// 导师模型
/// </summary>
public MentorModel Mentor { get; set; } = new("Player");
/// <summary>
/// 学生列表
/// </summary>
public List<StudentModel> Students { get; } = new();
/// <summary>
/// 职工列表
/// </summary>
public List<StaffModel> Staffs { get; } = new();
}
/// <summary>
/// 任务状态数据
/// </summary>
public sealed class TaskState
{
/// <summary>
/// 活动任务列表
/// </summary>
public List<TaskModel> ActiveTasks { get; } = new();
/// <summary>
/// 已完成任务列表
/// </summary>
public List<TaskModel> CompletedTasks { get; } = new();
/// <summary>
/// 失败任务列表
/// </summary>
public List<TaskModel> FailedTasks { get; } = new();
}
/// <summary>
/// 库存状态数据
/// </summary>
public sealed class InventoryState
{
/// <summary>
/// 物品计数
/// </summary>
public Dictionary<string, int> ItemCounts { get; } = new();
/// <summary>
/// 论文计数
/// </summary>
public Dictionary<PaperRank, int> PaperCounts { get; } = new();
}
/// <summary>
/// 协同状态数据
/// </summary>
public sealed class SynergyState
{
/// <summary>
/// 原型堆叠数
/// </summary>
public Dictionary<string, int> ArchetypeStacks { get; } = new();
/// <summary>
/// 角色堆叠数
/// </summary>
public Dictionary<string, int> RoleStacks { get; } = new();
/// <summary>
/// 激活的协同ID列表
/// </summary>
public List<string> ActiveSynergyIds { get; } = new();
/// <summary>
/// 激活的修正包
/// </summary>
public ModifierBundle ActiveModifiers { get; } = new();
}
/// <summary>
/// 肉鸽状态数据
/// </summary>
public sealed class RogueliteState
{
/// <summary>
/// 校友卡ID列表
/// </summary>
public List<string> AlumniCardIds { get; } = new();
/// <summary>
/// 遗产解锁ID列表
/// </summary>
public List<string> LegacyUnlockIds { get; } = new();
/// <summary>
/// 头衔保留等级
/// </summary>
public int TitleRetentionLevel { get; set; }
}
}

View File

@ -14,29 +14,29 @@ namespace Models;
/// </summary>
public enum ItemCategory
{
Facility,
Equipment,
Consumable
Facility, // 设施
Equipment, // 装备
Consumable // 消耗品
}
public enum FacilityPlacement
{
Lab,
ServerRoom,
Admin,
Library,
RestArea,
Field,
Global
Lab, // 实验室
ServerRoom, // 机房
Admin, // 行政区
Library, // 图书馆
RestArea, // 休息区
Field, // 场地
Global // 全局
}
public enum EquipmentSlot
{
Tool,
Accessory,
Body,
Head,
Hand
Tool, // 工具
Accessory, // 饰品
Body, // 身体
Head, // 头部
Hand // 手部
}
public sealed class ItemDefinition
@ -55,5 +55,4 @@ public sealed class ItemEffect
{
public ModifierBundle Modifiers { get; set; } = new();
public List<string> RuleIds { get; } = new();
}
}

View File

@ -14,22 +14,41 @@ namespace Models;
/// </summary>
public sealed class MentorModel
{
/// <summary>
/// 导师模式
/// </summary>
public enum MentorModeType
{
Worker,
Manager
Worker, // 打工人
Manager // 管理者
}
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; }
/// <summary>
/// 导师特有资源
/// </summary>
public MentorResources Resources { get; }
/// <summary>
/// 当前模式
/// </summary>
public MentorModeType Mode { get; set; } = MentorModeType.Worker;
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name
{
get => Core.Name;
set => Core.Name = value;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
public MentorModel(string name)
{
Core = new UnitModel(name);
@ -42,10 +61,13 @@ public sealed class MentorModel
/// </summary>
public sealed class MentorResources
{
/// <summary>
/// 精力值
/// </summary>
public StatusValue Energy { get; set; }
public MentorResources()
{
Energy = new StatusValue(100, 100, 0);
}
}
}

View File

@ -14,30 +14,68 @@ namespace Models;
/// </summary>
public sealed class AttributeModifier
{
/// <summary>
/// 属性类型
/// </summary>
public AttributeType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public float Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f;
}
public sealed class StatusModifier
{
/// <summary>
/// 状态类型
/// </summary>
public StatusType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public float Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f;
}
public sealed class ResourceModifier
{
/// <summary>
/// 资源类型
/// </summary>
public ResourceType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public int Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f;
}
public sealed class ModifierBundle
{
/// <summary>
/// 属性修正列表
/// </summary>
public List<AttributeModifier> AttributeModifiers { get; } = new();
/// <summary>
/// 状态修正列表
/// </summary>
public List<StatusModifier> StatusModifiers { get; } = new();
/// <summary>
/// 资源修正列表
/// </summary>
public List<ResourceModifier> ResourceModifiers { get; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new();
}
}

View File

@ -14,5 +14,4 @@ public sealed class PaperDefinition
{
public DefinitionHeader Header { get; set; } = new();
public PaperRank Rank { get; set; }
}
}

View File

@ -20,17 +20,32 @@ public sealed class PropertyValue
public const float DefaultMin = 0f;
public const float DefaultMax = 100f;
/// <summary>
/// 最小值
/// </summary>
public float Min { get; }
/// <summary>
/// 最大值
/// </summary>
public float Max { get; }
private float _value;
/// <summary>
/// 当前值(自动限制在 Min/Max 范围内)
/// </summary>
public float Value
{
get => _value;
set => _value = Clamp(value, Min, Max);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="value">初始值</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public PropertyValue(float value = 0, float min = DefaultMin, float max = DefaultMax)
{
if (max < min)
@ -44,13 +59,26 @@ public sealed class PropertyValue
Value = value;
}
/// <summary>
/// 构造函数(整数)
/// </summary>
/// <param name="value">初始值</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public PropertyValue(int value, float min = DefaultMin, float max = DefaultMax)
: this((float)value, min, max)
{
}
/// <summary>
/// 获取显示的整数值
/// </summary>
/// <returns>整数值</returns>
public int DisplayInt() => (int)_value;
/// <summary>
/// 获取归一化值 (0.0 - 1.0)
/// </summary>
public float Normalized
{
get
@ -60,10 +88,22 @@ public sealed class PropertyValue
}
}
/// <summary>
/// 克隆
/// </summary>
/// <returns>新实例</returns>
public PropertyValue Clone() => new(Value, Min, Max);
/// <summary>
/// 增加值
/// </summary>
/// <param name="delta">增量</param>
public void Add(float delta) => Value += delta;
/// <summary>
/// 减少值
/// </summary>
/// <param name="delta">减量</param>
public void Subtract(float delta) => Value -= delta;
public override string ToString() => DisplayInt().ToString();
@ -128,4 +168,4 @@ public sealed class PropertyValue
if (value > max) return max;
return value;
}
}
}

View File

@ -12,16 +12,27 @@ namespace Models;
/// </summary>
public enum RoguelitePerkType
{
AlumniCard,
LegacyProgress,
TitleRetention
AlumniCard, // 校友卡
LegacyProgress, // 遗产进度
TitleRetention // 头衔保留
}
public sealed class RoguelitePerkDefinition
{
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 天赋类型
/// </summary>
public RoguelitePerkType Type { get; set; }
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 最大等级
/// </summary>
public int MaxLevel { get; set; } = 1;
}
}

View File

@ -14,22 +14,43 @@ namespace Models;
/// </summary>
public sealed class StaffModel
{
/// <summary>
/// 职员类型
/// </summary>
public enum StaffType
{
PostDoc,
JuniorFaculty
PostDoc, // 博士后
JuniorFaculty // 青年教师
}
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; }
/// <summary>
/// 职员类型
/// </summary>
public StaffType Type { get; private set; }
/// <summary>
/// 动机数据
/// </summary>
public StaffMotivation Motivation { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name
{
get => Core.Name;
set => Core.Name = value;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="type">类型</param>
/// <param name="random">随机数生成器</param>
public StaffModel(string name, StaffType type, Random random = null)
{
Core = new UnitModel(name, random);
@ -38,10 +59,21 @@ public sealed class StaffModel
}
}
/// <summary>
/// 职员动机组件
/// </summary>
public sealed class StaffMotivation
{
/// <summary>
/// 野心
/// </summary>
public StatusValue Ambition { get; set; } = new(50, 100, 0);
/// <summary>
/// 忠诚度
/// </summary>
public StatusValue Loyalty { get; set; } = new(70, 100, 0);
/// <summary>
/// 合同剩余回合
/// </summary>
public StatusValue ContractTurns { get; set; } = new(6, 12, 0);
}
}

View File

@ -19,9 +19,18 @@ public sealed class StatusValue
{
private PropertyValue _current;
/// <summary>
/// 达到上限事件
/// </summary>
public event Action OnUpperThresholdReached;
/// <summary>
/// 达到下限事件
/// </summary>
public event Action OnLowerThresholdReached;
/// <summary>
/// 当前值
/// </summary>
public PropertyValue Current
{
get => _current;
@ -32,9 +41,18 @@ public sealed class StatusValue
}
}
/// <summary>
/// 上限阈值
/// </summary>
public float UpperThreshold { get; set; }
/// <summary>
/// 下限阈值
/// </summary>
public float LowerThreshold { get; set; }
/// <summary>
/// 构造函数(整数)
/// </summary>
public StatusValue(int current = 0, int upper = 100, int lower = 0)
{
_current = new PropertyValue(current, lower, upper);
@ -42,6 +60,9 @@ public sealed class StatusValue
LowerThreshold = lower;
}
/// <summary>
/// 构造函数(浮点数)
/// </summary>
public StatusValue(float current, float upper, float lower)
{
_current = new PropertyValue(current, lower, upper);
@ -49,6 +70,9 @@ public sealed class StatusValue
LowerThreshold = lower;
}
/// <summary>
/// 构造函数PropertyValue
/// </summary>
public StatusValue(PropertyValue current, float upperThreshold, float lowerThreshold)
{
_current = current ?? new PropertyValue(0);
@ -70,15 +94,21 @@ public sealed class StatusValue
}
}
/// <summary>
/// 增加值
/// </summary>
public void Add(float value)
{
_current.Add(value);
CheckThresholds();
}
/// <summary>
/// 减少值
/// </summary>
public void Subtract(float value)
{
_current.Subtract(value);
CheckThresholds();
}
}
}

View File

@ -18,25 +18,51 @@ namespace Models;
/// </summary>
public sealed class StudentModel
{
/// <summary>
/// 学生类型
/// </summary>
public enum StudentType
{
MasterCandidate,
DoctorCandidate
MasterCandidate, // 硕士生
DoctorCandidate // 博士生
}
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; }
/// <summary>
/// 学生类型
/// </summary>
public StudentType Type { get; private set; }
/// <summary>
/// 进度数据
/// </summary>
public StudentProgress Progress { get; }
/// <summary>
/// 贡献记录
/// </summary>
public StudentContributions Contributions { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name
{
get => Core.Name;
set => Core.Name = value;
}
/// <summary>
/// 特质列表(便捷访问)
/// </summary>
public List<string> Traits => Core.Tags.TraitIds;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="random">随机数生成器</param>
public StudentModel(string name, Random random = null)
{
var rng = random ?? Random.Shared;
@ -52,8 +78,17 @@ public sealed class StudentModel
/// </summary>
public sealed class StudentProgress
{
/// <summary>
/// 体力
/// </summary>
public StatusValue Stamina { get; set; }
/// <summary>
/// 忠诚度
/// </summary>
public StatusValue Loyalty { get; set; }
/// <summary>
/// 年级
/// </summary>
public PropertyValue Grade { get; set; }
public StudentProgress(StudentModel.StudentType type)
@ -70,5 +105,8 @@ public sealed class StudentProgress
/// </summary>
public sealed class StudentContributions
{
/// <summary>
/// 按任务ID索引的贡献值
/// </summary>
public Dictionary<Guid, PropertyValue> ByTask { get; } = new();
}
}

View File

@ -14,30 +14,62 @@ namespace Models;
/// </summary>
public sealed class ArchetypeDefinition
{
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 羁绊层级列表
/// </summary>
public List<SynergyTier> Tiers { get; } = new();
}
public sealed class RoleDefinition
{
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 羁绊层级列表
/// </summary>
public List<SynergyTier> Tiers { get; } = new();
// 如果是学科限定角色(如炼金术士/极客),在这里配置允许的学科 Id。
/// <summary>
/// 允许的学科ID列表
/// </summary>
public List<string> AllowedDisciplineIds { get; } = new();
}
public sealed class TraitDefinition
{
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new();
}
public sealed class SynergyTier
{
/// <summary>
/// 需求数量
/// </summary>
public int RequiredCount { get; set; }
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new();
}
}

View File

@ -77,4 +77,4 @@ public sealed class TaskRewardSnapshot
public int Reputation { get; set; }
public List<string> PaperIds { get; } = new();
public List<string> ItemIds { get; set; }
}
}

View File

@ -33,10 +33,10 @@ public enum TaskKind
public enum TaskDifficulty
{
Water,
Standard,
Hardcore,
BlackBox
Water, // 水
Standard, // 标准
Hardcore, // 硬核
BlackBox // 黑箱
}
public enum PaperRank
@ -97,5 +97,4 @@ public sealed class TaskRewards
public List<string> PaperIds { get; } = new();
public List<string> ItemIds { get; } = new();
public List<string> BuffIds { get; } = new();
}
}

View File

@ -18,6 +18,9 @@ namespace Models;
/// </summary>
public static class UnitComponents
{
/// <summary>
/// 网格位置组件
/// </summary>
public readonly struct GridPosition
{
public int X { get; }
@ -32,6 +35,9 @@ public static class UnitComponents
public static GridPosition Zero => new(0, 0);
}
/// <summary>
/// 单位身份组件
/// </summary>
public sealed class UnitIdentity
{
public Guid Id { get; } = Guid.NewGuid();
@ -45,6 +51,9 @@ public static class UnitComponents
}
}
/// <summary>
/// 单位属性组件
/// </summary>
public sealed class UnitAttributes
{
public PropertyValue Academic { get; set; }
@ -66,6 +75,9 @@ public static class UnitComponents
}
}
/// <summary>
/// 单位状态组件
/// </summary>
public sealed class UnitStatuses
{
public StatusValue Stress { get; set; }
@ -82,6 +94,9 @@ public static class UnitComponents
}
}
/// <summary>
/// 单位标签组件
/// </summary>
public sealed class UnitTags
{
public string DisciplineId { get; set; }
@ -92,19 +107,28 @@ public static class UnitComponents
public bool HasTrait(string traitId) => TraitIds.Contains(traitId);
}
/// <summary>
/// 单位指派组件
/// </summary>
public sealed class UnitAssignment
{
public Guid? CurrentTaskId { get; set; }
}
/// <summary>
/// 单位位置组件
/// </summary>
public sealed class UnitPlacement
{
public GridPosition Current { get; set; } = GridPosition.Zero;
public GridPosition Target { get; set; } = GridPosition.Zero;
}
/// <summary>
/// 单位装备组件
/// </summary>
public sealed class UnitEquipment
{
public List<string> EquippedItemIds { get; } = new();
}
}
}

View File

@ -17,20 +17,49 @@ namespace Models;
/// </summary>
public sealed class UnitModel
{
/// <summary>
/// 身份组件
/// </summary>
public UnitComponents.UnitIdentity Identity { get; }
/// <summary>
/// 属性组件
/// </summary>
public UnitComponents.UnitAttributes Attributes { get; }
/// <summary>
/// 状态组件
/// </summary>
public UnitComponents.UnitStatuses Statuses { get; }
/// <summary>
/// 标签组件
/// </summary>
public UnitComponents.UnitTags Tags { get; }
/// <summary>
/// 指派组件
/// </summary>
public UnitComponents.UnitAssignment Assignment { get; }
/// <summary>
/// 位置组件
/// </summary>
public UnitComponents.UnitPlacement Placement { get; }
/// <summary>
/// 装备组件
/// </summary>
public UnitComponents.UnitEquipment Equipment { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name
{
get => Identity.Name;
set => Identity.Name = value;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="random">随机数生成器</param>
public UnitModel(string name, Random random = null)
{
Identity = new UnitComponents.UnitIdentity(name);
@ -41,4 +70,4 @@ public sealed class UnitModel
Placement = new UnitComponents.UnitPlacement();
Equipment = new UnitComponents.UnitEquipment();
}
}
}

View File

@ -2,6 +2,9 @@ using Godot;
using System;
using System.Collections.Generic;
/// <summary>
/// 玩家数据管理类
/// </summary>
public partial class Player : Node
{
/// <summary>
@ -37,12 +40,23 @@ public partial class Player : Node
public int Total => Facility + Operational + Labor;
}
/// <summary>
/// 时间轴类型
/// </summary>
public class TimelineType
{
/// <summary>
/// 内部日期
/// </summary>
public DateOnly InternalDate { get; set; }
private Dictionary<DateOnly, HashSet<Guid>> _events;
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="date">日期</param>
/// <param name="eventUuid">事件UUID</param>
public void Subscribe(DateOnly date, Guid eventUuid)
{
_events ??= new Dictionary<DateOnly, HashSet<Guid>>();
@ -50,6 +64,11 @@ public partial class Player : Node
_events[date].Add(eventUuid);
}
/// <summary>
/// 取消订阅事件
/// </summary>
/// <param name="date">日期</param>
/// <param name="eventUuid">事件UUID</param>
public void Unsubscribe(DateOnly date, Guid eventUuid)
{
if (_events == null) return;
@ -57,6 +76,9 @@ public partial class Player : Node
_events[date].Remove(eventUuid);
}
/// <summary>
/// 进入下一天
/// </summary>
private void NextDay()
{
if (_events == null) return;
@ -78,27 +100,53 @@ public partial class Player : Node
}
}
/// <summary>
/// 附加计时器
/// </summary>
/// <param name="ticker">计时器</param>
public void Attach(Timer ticker)
{
ticker.Timeout += NextDay;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="startDate">开始日期</param>
public TimelineType(DateOnly startDate)
{
InternalDate = startDate;
}
/// <summary>
/// 事件触发委托
/// </summary>
public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUuid);
/// <summary>
/// 事件触发事件
/// </summary>
public event EventTriggeredEventHandler OnEventTriggered;
/// <summary>
/// 日期变更委托
/// </summary>
public delegate void DayChangedEventHandler(DateOnly date);
/// <summary>
/// 日期变更事件
/// </summary>
public event DayChangedEventHandler OnDayChanged;
}
/// <summary>
/// 预算实例
/// </summary>
public static BudgetType Budget { get; set; } = new();
/// <summary>
/// 时间轴实例
/// </summary>
public static readonly TimelineType Timeline = new(DateOnly.Parse("2024/11/17"));
/// <summary>
@ -117,13 +165,20 @@ public partial class Player : Node
public static DateOnly Date { get; set; }
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
}
}
}

View File

@ -1,18 +1,31 @@
using Godot;
using System;
/// <summary>
/// 资源管理类
/// </summary>
public partial class Res : Node
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
}
/// <summary>
/// 资源类型枚举
/// </summary>
public enum Type
{
Accessory,
@ -23,16 +36,32 @@ public partial class Res : Node
Phone
}
/// <summary>
/// 获取随机资源路径
/// </summary>
/// <param name="resType">资源类型</param>
/// <returns>资源路径</returns>
public static string GetRandom(Type resType)
{
return GetRandom(resType, false);
}
/// <summary>
/// 转换为16x16精灵路径
/// </summary>
/// <param name="path">原路径</param>
/// <returns>16x16路径</returns>
private static string To16Path(string path)
{
return path.Replace("_48x48_", "_").Replace("_48x48", "");
}
/// <summary>
/// 获取随机资源路径可选16x16
/// </summary>
/// <param name="resType">资源类型</param>
/// <param name="use16X16Sprites">是否使用16x16精灵</param>
/// <returns>资源路径</returns>
public static string GetRandom(Type resType, bool use16X16Sprites)
{
var resources = allResources[(int)resType];
@ -48,6 +77,9 @@ public partial class Res : Node
return ResourceLoader.Exists(path16) ? path16 : path;
}
/// <summary>
/// 身体资源列表
/// </summary>
public static readonly string[] Body =
[
"res://resources/characters/bodies/Body_48x48_01.png",
@ -60,6 +92,9 @@ public partial class Res : Node
"res://resources/characters/bodies/Body_48x48_08.png"
];
/// <summary>
/// 饰品资源列表
/// </summary>
public static readonly string[] Accessory =
[
"res://resources/characters/accessories/Accessory_01_Ladybug_48x48_01.png",
@ -145,6 +180,9 @@ public partial class Res : Node
"res://resources/characters/accessories/Accessory_19_Party_Cone_48x48_04.png"
];
/// <summary>
/// 眼睛资源列表
/// </summary>
public static readonly string[] Eye =
[
"res://resources/characters/eyes/Eyes_48x48_01.png",
@ -156,6 +194,9 @@ public partial class Res : Node
"res://resources/characters/eyes/Eyes_48x48_07.png"
];
/// <summary>
/// 发型资源列表
/// </summary>
public static readonly string[] Hair =
[
"res://resources/characters/hairstyles/Hairstyle_01_48x48_01.png",
@ -360,6 +401,9 @@ public partial class Res : Node
"res://resources/characters/hairstyles/Hairstyle_29_48x48_06.png"
];
/// <summary>
/// 服装资源列表
/// </summary>
public static readonly string[] Outfit =
[
"res://resources/characters/outfits/Outfit_01_48x48_01.png",
@ -496,6 +540,9 @@ public partial class Res : Node
"res://resources/characters/outfits/Outfit_33_48x48_03.png"
];
/// <summary>
/// 手机资源列表
/// </summary>
public static readonly string[] Smartphone =
[
"res://resources/characters/smartphones/Smartphone_48x48_1.png",
@ -515,4 +562,4 @@ public partial class Res : Node
Smartphone
];
}
}

View File

@ -1,9 +1,15 @@
using Godot;
using System;
/// <summary>
/// 场景过渡控制器
/// </summary>
public partial class SceneTransit : CanvasLayer
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
Layer = -1;
@ -11,10 +17,21 @@ public partial class SceneTransit : CanvasLayer
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
}
/// <summary>
/// 切换场景
/// </summary>
/// <param name="nextScene">下一场景路径</param>
/// <param name="needLoadResources">是否需要加载资源</param>
/// <param name="animation">过渡动画名称</param>
/// <param name="waitSecond">等待时间</param>
public async void Transit(string nextScene, bool needLoadResources = true, string animation = "transit", float waitSecond = 0)
{
SetProcess(true);
@ -39,4 +56,4 @@ public partial class SceneTransit : CanvasLayer
SetProcess(false);
}
}
}

View File

@ -5,15 +5,34 @@ using System.Linq;
using Models;
// ReSharper disable CheckNamespace
/// <summary>
/// 学生角色控制器
/// </summary>
public partial class Student : CharacterBody2D
{
/// <summary>
/// 移动速度
/// </summary>
public float Speed { get; set; } = 8.0f;
/// <summary>
/// 跳跃速度
/// </summary>
public const float JumpVelocity = -400.0f;
/// <summary>
/// 是否使用16x16精灵
/// </summary>
[Export] public bool Use16X16Sprites { get; set; }
// --- MVP: Model Binding ---
/// <summary>
/// 学生数据模型
/// </summary>
public StudentModel Model { get; private set; }
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">学生模型</param>
public void BindData(StudentModel model)
{
Model = model;
@ -21,15 +40,35 @@ public partial class Student : CharacterBody2D
}
// --------------------------
/// <summary>
/// 下一个状态类型(未使用)
/// </summary>
public int NextType = -1;
private Queue<Vector2I> _pathToGo = new();
private AnimationPlayer _animationPlayer;
/// <summary>
/// 角色状态枚举
/// </summary>
public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp }
/// <summary>
/// 当前状态
/// </summary>
public CharacterState State { get; set; } = CharacterState.Idle;
/// <summary>
/// 方向枚举
/// </summary>
public enum Direction { Up, Down, Left, Right }
/// <summary>
/// 目标方向
/// </summary>
public Direction TargetDirection { get; set; } = Direction.Up;
/// <summary>
/// 状态队列
/// </summary>
public Queue<CharacterState> StateQueue { get; set; } = new();
private Vector2I _targetSpecialPosition;
@ -54,6 +93,11 @@ public partial class Student : CharacterBody2D
State = StateQueue.Dequeue();
GlobalPosition = _lastWalkablePosition;
}
/// <summary>
/// 物理处理
/// </summary>
/// <param name="delta">时间间隔</param>
public override void _PhysicsProcess(double delta)
{
if (StateQueue.Count > 0) {
@ -141,6 +185,9 @@ public partial class Student : CharacterBody2D
// MoveAndSlide();
}
/// <summary>
/// 准备就绪时调用
/// </summary>
public override void _Ready()
{
base._Ready();
@ -163,6 +210,10 @@ public partial class Student : CharacterBody2D
}
}
/// <summary>
/// 移动跟随路径
/// </summary>
/// <param name="path">路径点列表</param>
public void MoveFollowPath(List<Vector2I> path)
{
foreach (var p in path)
@ -171,8 +222,14 @@ public partial class Student : CharacterBody2D
}
}
/// <summary>
/// 关联的格子ID
/// </summary>
public Guid CubeId;
/// <summary>
/// 随机前往某处
/// </summary>
public void GoTo() {
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
{
@ -201,6 +258,9 @@ public partial class Student : CharacterBody2D
RandomChangeBody();
}
/// <summary>
/// 前往座位
/// </summary>
public void GoToSeat() {
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
{
@ -248,6 +308,9 @@ public partial class Student : CharacterBody2D
RandomChangeBody();
}
/// <summary>
/// 随机更换外观
/// </summary>
private void RandomChangeBody()
{
GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites));
@ -257,4 +320,4 @@ public partial class Student : CharacterBody2D
GetNode<Sprite2D>("parts/accessory").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Accessory, Use16X16Sprites));
GetNode<Sprite2D>("parts/smartphone").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Phone, Use16X16Sprites));
}
}
}

View File

@ -2,20 +2,33 @@ using Godot;
using System;
using System.IO;
/// <summary>
/// 学生姓名生成器
/// </summary>
public partial class StudentName : Node
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta)
{
}
public string GenerateName()
{
string[] = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"};
/// <summary>
/// 生成随机姓名
/// </summary>
/// <returns>随机姓名</returns>
public string GenerateName()
{ string[] = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"};
string[] = { "金", "猪", "兔", "猫", "鱼", "蛋", "胖", "大", "傻", "酷", "蠢", "聪", "萌", "暴","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };
string[] = { "子", "郎", "妹", "宝", "儿", "汉", "君", "爷", "娃", "猪", "鬼", "鸟", "仔", "蛋","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };

View File

@ -1,26 +1,32 @@
using Godot;
using System;
/// <summary>
/// 测试地图层
/// </summary>
public partial class TestMap : TileMapLayer
{
/// <summary>
/// The last cell that was highlighted. Used to avoid unnecessary updates.
/// 上一次高亮的单元格。用于避免不必要的更新。
/// </summary>
private Vector2I _lastHighlightCell;
private readonly Vector2I _highlightTileCoord = new(0, 5);
private readonly Vector2I _vector2INegOne = new(-1, -1);
// Called when the node enters the scene tree for the first time.
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready()
{
// Initialize the lastHighlightCell to an invalid value.
// 初始化最后高亮单元格为无效值。
_lastHighlightCell = _vector2INegOne;
}
/// <summary>
/// Called every frame.
/// 每帧更新。
/// </summary>
/// <param name="delta">the elapsed time since the previous frame.</param>
/// <param name="delta">距离上一帧的时间间隔。</param>
public override void _Process(double delta)
{
Vector2 mousePos = GetLocalMousePosition();
@ -36,5 +42,4 @@ public partial class TestMap : TileMapLayer
SetCell(cell, 0, _highlightTileCoord);
}
}
}
}

View File

@ -3,23 +3,39 @@ using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 图块映射辅助类
/// </summary>
public partial class TileMapping : Node
{
/// <summary>
/// From tile coordinates to pixel coordinates. If null, use the offset instead.
/// 从图块坐标到像素坐标的映射。如果不为空,则使用此映射。
/// </summary>
private Dictionary<Vector2I, Vector2I> _pixelMap;
/// <summary>
/// If pixelMap is null, use this offset to convert tile coordinates to pixel coordinates.
/// 如果 pixelMap 为空,使用此偏移量将图块坐标转换为像素坐标。
/// </summary>
private Vector2I _pixelOffset;
/// <summary>
/// 图块区域
/// </summary>
private Rect2I _tileRect;
/// <summary>
/// 构造函数,使用坐标映射字典
/// </summary>
/// <param name="map">坐标映射字典</param>
public TileMapping(Dictionary<Vector2I, Vector2I> map) {
_pixelMap = map;
_pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue);
}
/// <summary>
/// 构造函数,使用偏移量和区域
/// </summary>
/// <param name="offset">偏移量</param>
/// <param name="rect">区域</param>
public TileMapping(Vector2I offset, Rect2I rect) {
_pixelMap = null;
_pixelOffset = offset;
@ -28,6 +44,10 @@ public partial class TileMapping : Node
public TileMapping() {}
/// <summary>
/// 应用映射到TileMapLayer
/// </summary>
/// <param name="layer">TileMapLayer</param>
public void Apply(TileMapLayer layer) {
// if pixelMap is not null, use all the pairs in pixelMap to set the tiles.
if (_pixelMap != null) {
@ -46,4 +66,4 @@ public partial class TileMapping : Node
}
}
}
}
}