Player movement. To Fix: player out of area.

This commit is contained in:
wjsjwr 2025-12-27 23:00:14 +08:00
parent f3a4e31f31
commit bce6fffc3e
20 changed files with 635 additions and 188 deletions

View File

@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="最强导师" config/name="最强导师"
run/main_scene="uid://bmxk4pi4sd1rd" run/main_scene="uid://b0cu4fa7vohmw"
config/features=PackedStringArray("4.5", "C#", "GL Compatibility") config/features=PackedStringArray("4.5", "C#", "GL Compatibility")
config/icon="res://icon.svg" config/icon="res://icon.svg"

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1,5 +1,6 @@
using Godot; using Godot;
using System; using System;
using System.Collections.Generic;
public partial class CampusController : Node2D public partial class CampusController : Node2D
{ {
@ -8,6 +9,16 @@ public partial class CampusController : Node2D
private Button _taskToggle; private Button _taskToggle;
private Button _logToggle; private Button _logToggle;
[Export] public PackedScene StudentScene { get; set; }
[Export] public int StudentCount { get; set; } = 6;
[Export] public float CoverageStep { get; set; } = 48.0f;
[Export] public int MaxCoveragePoints { get; set; } = 200;
private NavigationRegion2D _navigationRegion;
private Node2D _studentsRoot;
private readonly List<Vector2> _coveragePoints = new();
private bool _spawnPending = true;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready()
{ {
@ -24,11 +35,24 @@ public partial class CampusController : Node2D
_taskToggle.Toggled += OnTaskToggled; _taskToggle.Toggled += OnTaskToggled;
_logToggle.Toggled += OnLogToggled; _logToggle.Toggled += OnLogToggled;
// 导航区域与学生容器初始化
_navigationRegion = GetNodeOrNull<NavigationRegion2D>("Sprite2D/NavigationRegion2D");
_studentsRoot = GetNodeOrNull<Node2D>("Students");
if (_studentsRoot == null)
{
_studentsRoot = new Node2D { Name = "Students" };
AddChild(_studentsRoot);
}
// 等待导航地图同步完成后再生成学生
_spawnPending = true;
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) public override void _Process(double delta)
{ {
TrySpawnStudents();
} }
private void OnTaskToggled(bool pressed) private void OnTaskToggled(bool pressed)
@ -61,4 +85,158 @@ public partial class CampusController : Node2D
tween.TweenCallback(Callable.From(() => container.Visible = false)); tween.TweenCallback(Callable.From(() => container.Visible = false));
} }
} }
}
private void TrySpawnStudents()
{
if (!_spawnPending)
{
return;
}
// 已生成过学生则不重复生成
if (_studentsRoot != null && _studentsRoot.GetChildCount() > 0)
{
_spawnPending = false;
return;
}
if (StudentScene == null)
{
StudentScene = ResourceLoader.Load<PackedScene>("res://scenes/student_16_native.tscn");
}
if (_navigationRegion == null || _navigationRegion.NavigationPolygon == null)
{
GD.PushWarning("校园导航区域未配置或缺失导航多边形,无法生成巡游学生。");
_spawnPending = false;
return;
}
// 导航地图可能还未就绪,需要等待同步完成后再采样
var map = GetNavigationMap();
if (!map.IsValid)
{
return;
}
// 强制刷新一次导航数据,确保首帧同步
NavigationServer2D.MapForceUpdate(map);
if (NavigationServer2D.MapGetIterationId(map) == 0)
{
return;
}
SpawnStudents(map);
_spawnPending = false;
}
private void SpawnStudents(Rid map)
{
_coveragePoints.Clear();
_coveragePoints.AddRange(BuildCoveragePoints(map));
if (_coveragePoints.Count == 0)
{
GD.PushWarning("未采样到可行走区域,跳过学生生成。");
return;
}
for (int i = 0; i < StudentCount; i++)
{
var instance = StudentScene.Instantiate();
if (instance is not CampusStudent student)
{
GD.PushWarning("student_16_native.tscn 需要挂载 CampusStudent 脚本。");
instance.QueueFree();
continue;
}
_studentsRoot.AddChild(student);
student.Name = $"CampusStudent_{i + 1}";
// 随机放置在可行走区域,并设置不同的巡游起点
var randomIndex = GD.RandRange(0, _coveragePoints.Count - 1);
student.GlobalPosition = _coveragePoints[randomIndex];
student.ConfigurePatrol(_coveragePoints, i * 7);
}
}
private List<Vector2> BuildCoveragePoints(Rid map)
{
var points = new List<Vector2>();
var polygon = _navigationRegion.NavigationPolygon;
if (polygon == null || polygon.Vertices.Length == 0)
{
return points;
}
// 根据导航多边形顶点计算采样范围
var min = polygon.Vertices[0];
var max = polygon.Vertices[0];
foreach (var v in polygon.Vertices)
{
min = new Vector2(Mathf.Min(min.X, v.X), Mathf.Min(min.Y, v.Y));
max = new Vector2(Mathf.Max(max.X, v.X), Mathf.Max(max.Y, v.Y));
}
var step = Mathf.Max(8.0f, CoverageStep);
var minDistance = step * 0.45f;
for (float x = min.X; x <= max.X; x += step)
{
for (float y = min.Y; y <= max.Y; y += step)
{
var local = new Vector2(x, y);
var global = _navigationRegion.ToGlobal(local);
var closest = NavigationServer2D.MapGetClosestPoint(map, global);
if (closest.DistanceTo(global) > minDistance)
{
continue;
}
if (!HasNearbyPoint(points, closest, minDistance))
{
points.Add(closest);
if (points.Count >= MaxCoveragePoints)
{
return points;
}
}
}
}
// 兜底:至少给一个可行走点
if (points.Count == 0)
{
var centerLocal = (min + max) * 0.5f;
var centerGlobal = _navigationRegion.ToGlobal(centerLocal);
points.Add(NavigationServer2D.MapGetClosestPoint(map, centerGlobal));
}
return points;
}
private Rid GetNavigationMap()
{
var map = NavigationServer2D.RegionGetMap(_navigationRegion.GetRid());
if (!map.IsValid)
{
var world = GetWorld2D();
if (world != null)
{
map = world.NavigationMap;
}
}
return map;
}
private static bool HasNearbyPoint(List<Vector2> points, Vector2 candidate, float minDistance)
{
for (int i = 0; i < points.Count; i++)
{
if (points[i].DistanceTo(candidate) <= minDistance)
{
return true;
}
}
return false;
}
}

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=8 format=3 uid="uid://b0cu4fa7vohmw"] [gd_scene load_steps=9 format=3 uid="uid://b0cu4fa7vohmw"]
[ext_resource type="Script" uid="uid://ew4ig6hnrsau" path="res://scenes/CampusController.cs" id="1_controller"] [ext_resource type="Script" uid="uid://ew4ig6hnrsau" path="res://scenes/CampusController.cs" id="1_controller"]
[ext_resource type="PackedScene" uid="uid://cf6b1t8pujosf" path="res://scenes/ui-elements/log_panel.tscn" id="1_hi2p7"] [ext_resource type="PackedScene" uid="uid://cf6b1t8pujosf" path="res://scenes/ui-elements/log_panel.tscn" id="1_hi2p7"]
@ -6,6 +6,15 @@
[ext_resource type="PackedScene" uid="uid://db2qcx61nc0q4" path="res://scenes/ui-elements/top-bar.tscn" id="2_p4tmp"] [ext_resource type="PackedScene" uid="uid://db2qcx61nc0q4" path="res://scenes/ui-elements/top-bar.tscn" id="2_p4tmp"]
[ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"] [ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_8u8vn"]
vertices = PackedVector2Array(960, 544, 0, 544, 416, 512, 464, 512, 384, 512, 384, 464, 176, 496, 304, 464, 144, 496, 200, 352, 200, 264, 232, 304, 232, 464, 232, 288, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 224, 800, 288, 912, 304, 912, 288, 936, 288, 952, 336, 936, 184, 952, 184, 832, 336, 832, 304, 832, 512, 800, 512, 576, 288, 576, 304, 544, 304, 544, 288, 960, 512, 120, 360, 128, 360, 128, 400, 192, 400, 192, 352, 288, 464, 288, 448, 304, 448, 176, 464, 144, 464, 0, 496, 96, 496, 96, 288, 120, 264, 0, 288, 0, 0, 32, 0, 32, 208, 416, 288, 464, 288)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 2, 1), PackedInt32Array(5, 4, 1, 6, 7), PackedInt32Array(8, 6, 1), PackedInt32Array(9, 10, 11, 12), PackedInt32Array(13, 11, 10), PackedInt32Array(14, 15, 16, 17), PackedInt32Array(18, 19, 20, 21), PackedInt32Array(22, 23, 24, 25), PackedInt32Array(26, 27, 28, 29), PackedInt32Array(30, 31, 32), PackedInt32Array(32, 33, 34, 35), PackedInt32Array(36, 37, 38, 39), PackedInt32Array(40, 41, 42, 43), PackedInt32Array(43, 44, 45, 46), PackedInt32Array(40, 43, 46), PackedInt32Array(47, 40, 46), PackedInt32Array(39, 47, 46, 48), PackedInt32Array(49, 50, 51, 52), PackedInt32Array(51, 53, 54, 52), PackedInt32Array(49, 52, 55, 56), PackedInt32Array(55, 57, 58, 48), PackedInt32Array(56, 55, 48), PackedInt32Array(46, 56, 48), PackedInt32Array(39, 48, 59, 30), PackedInt32Array(59, 60, 61, 62), PackedInt32Array(57, 63, 0), PackedInt32Array(64, 65, 66), PackedInt32Array(67, 68, 9, 12), PackedInt32Array(69, 70, 71, 7), PackedInt32Array(69, 7, 6), PackedInt32Array(12, 69, 6), PackedInt32Array(12, 6, 72, 67), PackedInt32Array(66, 67, 72, 73), PackedInt32Array(1, 74, 75), PackedInt32Array(8, 1, 75), PackedInt32Array(73, 8, 75, 76, 66), PackedInt32Array(64, 66, 76), PackedInt32Array(77, 64, 76), PackedInt32Array(78, 79, 80, 81), PackedInt32Array(76, 78, 81, 14, 77), PackedInt32Array(10, 77, 14, 17), PackedInt32Array(10, 17, 18, 21, 13), PackedInt32Array(82, 13, 21, 22), PackedInt32Array(82, 22, 25), PackedInt32Array(30, 32, 35, 39), PackedInt32Array(82, 25, 26, 83), PackedInt32Array(35, 36, 39), PackedInt32Array(29, 30, 59), PackedInt32Array(26, 29, 59, 62), PackedInt32Array(26, 62, 83), PackedInt32Array(82, 83, 3, 2), PackedInt32Array(0, 3, 58), PackedInt32Array(0, 58, 57)])
outlines = Array[PackedVector2Array]([PackedVector2Array(0, 0, 32, 0, 32, 208, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 832, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 304, 912, 304, 912, 288, 936, 288, 936, 184, 952, 184, 952, 336, 832, 336, 832, 512, 960, 512, 960, 544, 0, 544, 0, 496, 96, 496, 96, 288, 0, 288), PackedVector2Array(144, 496, 176, 496, 176, 464, 144, 464), PackedVector2Array(128, 400, 192, 400, 192, 352, 200, 352, 200, 264, 120, 264, 120, 360, 128, 360), PackedVector2Array(232, 304, 232, 464, 288, 464, 288, 448, 304, 448, 304, 464, 384, 464, 384, 512, 416, 512, 416, 288, 232, 288), PackedVector2Array(464, 288, 464, 512, 800, 512, 800, 288, 576, 288, 576, 304, 544, 304, 544, 288), PackedVector2Array(144, 416, 176, 416, 176, 448, 144, 448)])
parsed_geometry_type = 1
parsed_collision_mask = 4294967294
source_geometry_mode = 1
agent_radius = 8.0
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4gjr3"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4gjr3"]
texture = ExtResource("1_p4tmp") texture = ExtResource("1_p4tmp")
use_texture_padding = false use_texture_padding = false
@ -2000,16 +2009,22 @@ script = ExtResource("1_controller")
texture = ExtResource("1_p4tmp") texture = ExtResource("1_p4tmp")
centered = false centered = false
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="Sprite2D"]
navigation_polygon = SubResource("NavigationPolygon_8u8vn")
[node name="Log" parent="." instance=ExtResource("1_hi2p7")] [node name="Log" parent="." instance=ExtResource("1_hi2p7")]
visible = false
offset_left = 655.0 offset_left = 655.0
offset_top = 375.0 offset_top = 375.0
offset_right = 955.0 offset_right = 955.0
offset_bottom = 535.0 offset_bottom = 535.0
[node name="TopBar" parent="." instance=ExtResource("2_p4tmp")] [node name="TopBar" parent="." instance=ExtResource("2_p4tmp")]
visible = false
offset_bottom = 55.0 offset_bottom = 55.0
[node name="Task" parent="." instance=ExtResource("3_4gjr3")] [node name="Task" parent="." instance=ExtResource("3_4gjr3")]
visible = false
offset_left = 4.0 offset_left = 4.0
offset_top = 216.0 offset_top = 216.0
offset_right = 305.0 offset_right = 305.0

View File

@ -1,6 +1,6 @@
[gd_scene load_steps=23 format=3 uid="uid://bmxk4pi4sd1rd"] [gd_scene load_steps=23 format=3 uid="uid://bmxk4pi4sd1rd"]
[ext_resource type="Script" uid="uid://bcy30mq13r02a" path="res://scripts/Student.cs" id="1_oesea"] [ext_resource type="Script" uid="uid://d2m5sak3n7q1s" path="res://scripts/CampusStudent.cs" id="1_oesea"]
[ext_resource type="Texture2D" uid="uid://ckjn1gxbn26iq" path="res://resources/characters/bodies/Body_01.png" id="3_jhea4"] [ext_resource type="Texture2D" uid="uid://ckjn1gxbn26iq" path="res://resources/characters/bodies/Body_01.png" id="3_jhea4"]
[ext_resource type="Script" uid="uid://bp2483mr5lay" path="res://scripts/StudentName.cs" id="8_kvqca"] [ext_resource type="Script" uid="uid://bp2483mr5lay" path="res://scripts/StudentName.cs" id="8_kvqca"]
[ext_resource type="Texture2D" uid="uid://bvngikeasxn3n" path="res://resources/characters/accessories/Accessory_06_Policeman_Hat_01.png" id="30_npp82"] [ext_resource type="Texture2D" uid="uid://bvngikeasxn3n" path="res://resources/characters/accessories/Accessory_06_Policeman_Hat_01.png" id="30_npp82"]
@ -10,7 +10,7 @@
[ext_resource type="Texture2D" uid="uid://bscmf27jf2qf0" path="res://resources/characters/smartphones/Smartphone_1.png" id="435_7vwql"] [ext_resource type="Texture2D" uid="uid://bscmf27jf2qf0" path="res://resources/characters/smartphones/Smartphone_1.png" id="435_7vwql"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_opr6h"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_opr6h"]
size = Vector2(16, 32) size = Vector2(16, 16)
[sub_resource type="Animation" id="Animation_f0f87"] [sub_resource type="Animation" id="Animation_f0f87"]
length = 0.001 length = 0.001
@ -877,10 +877,8 @@ _data = {
[node name="Student" type="CharacterBody2D"] [node name="Student" type="CharacterBody2D"]
z_index = 2 z_index = 2
script = ExtResource("1_oesea") script = ExtResource("1_oesea")
Use16x16Sprites = true
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -8)
shape = SubResource("RectangleShape2D_opr6h") shape = SubResource("RectangleShape2D_opr6h")
[node name="parts" type="Node2D" parent="."] [node name="parts" type="Node2D" parent="."]
@ -935,3 +933,11 @@ autoplay = "RESET"
[node name="StudentName" type="Node" parent="."] [node name="StudentName" type="Node" parent="."]
script = ExtResource("8_kvqca") script = ExtResource("8_kvqca")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
path_desired_distance = 4.0
target_desired_distance = 4.0
path_max_distance = 5000.0
path_postprocessing = 1
radius = 8.0
debug_enabled = true

246
scripts/CampusStudent.cs Normal file
View File

@ -0,0 +1,246 @@
using System.Collections.Generic;
using System.Diagnostics;
using Godot;
public partial class CampusStudent : CharacterBody2D
{
private Sprite2D _accessory;
private AnimationPlayer _animationPlayer;
private Sprite2D _body;
private Sprite2D _eye;
private Sprite2D _hairstyle;
private double _lastDelta;
private FacingDirection _lastFacing = FacingDirection.Down;
private Vector2 _lastPosition;
private NavigationAgent2D _navigationAgent;
private Sprite2D _outfit;
private bool _patrolConfigured;
private int _patrolIndex;
private List<Vector2> _patrolPoints = new();
private Sprite2D _smartphone;
private float _stuckTimer;
[Export] public float MoveSpeed { get; set; } = 60.0f;
[Export] public float TargetReachDistance { get; set; } = 6.0f;
[Export] public bool Use16X16Sprites { get; set; } = true;
[Export] public bool EnableAvoidance { get; set; } = true;
[Export] public float StuckRepathSeconds { get; set; } = 0.6f;
[Export] public float StuckDistanceEpsilon { get; set; } = 2.0f;
public override void _Ready()
{
_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");
_animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
CacheSprites();
if (_navigationAgent != null)
{
// 让寻路点更“贴近目标”,避免走到边缘时抖动
_navigationAgent.PathDesiredDistance = TargetReachDistance;
_navigationAgent.TargetDesiredDistance = TargetReachDistance;
_navigationAgent.AvoidanceEnabled = EnableAvoidance;
if (EnableAvoidance)
// 开启避让时使用安全速度回调进行移动
_navigationAgent.VelocityComputed += OnVelocityComputed;
if (_patrolConfigured) AdvanceTarget();
}
PlayIdleAnimation();
_lastPosition = GlobalPosition;
}
public override void _PhysicsProcess(double delta)
{
_lastDelta = delta;
if (_navigationAgent == null || _patrolPoints.Count == 0)
{
PlayIdleAnimation();
return;
}
// 到达目标点或无路可走时,切换到下一个巡游点
if (_navigationAgent.IsNavigationFinished() || _navigationAgent.DistanceToTarget() <= TargetReachDistance)
AdvanceTarget();
var nextPosition = _navigationAgent.GetNextPathPosition();
var toNext = nextPosition - GlobalPosition;
if (toNext.LengthSquared() < 0.01f)
{
if (!EnableAvoidance)
{
Velocity = Vector2.Zero;
MoveAndSlide();
}
PlayIdleAnimation();
UpdateStuckTimer(delta);
return;
}
var direction = toNext.Normalized();
var desiredVelocity = direction * MoveSpeed;
if (EnableAvoidance)
{
// 交给导航系统做群体避让
_navigationAgent.Velocity = desiredVelocity;
}
else
{
// 未启用避让时直接移动
Velocity = desiredVelocity;
MoveAndSlide();
UpdateFacingAnimation(Velocity);
UpdateStuckTimer(delta);
}
}
public void ConfigurePatrol(List<Vector2> points, int startIndex)
{
_patrolPoints = points ?? new List<Vector2>();
if (_patrolPoints.Count == 0) return;
_patrolIndex = (startIndex % _patrolPoints.Count + _patrolPoints.Count) % _patrolPoints.Count;
_patrolConfigured = true;
ApplyRandomTheme();
if (_navigationAgent != null) AdvanceTarget();
}
public void ApplyRandomTheme()
{
// 随机替换身体与配件贴图,形成不同主题外观
if (_body == null) CacheSprites();
Debug.Assert(_body != null, nameof(_body) + " != null");
_body.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites));
_hairstyle.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Hair, Use16X16Sprites));
_outfit.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Outfit, Use16X16Sprites));
_eye.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Eye, Use16X16Sprites));
_accessory.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Accessory, Use16X16Sprites));
_smartphone.Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Phone, Use16X16Sprites));
}
private void CacheSprites()
{
// 缓存子节点引用,避免每帧查找
_body = GetNode<Sprite2D>("parts/body");
_hairstyle = GetNode<Sprite2D>("parts/hairstyle");
_outfit = GetNode<Sprite2D>("parts/outfit");
_eye = GetNode<Sprite2D>("parts/eye");
_accessory = GetNode<Sprite2D>("parts/accessory");
_smartphone = GetNode<Sprite2D>("parts/smartphone");
}
private void AdvanceTarget()
{
if (_patrolPoints.Count == 0 || _navigationAgent == null) return;
// 避免当前点过近导致原地抖动,最多尝试一轮
for (var i = 0; i < _patrolPoints.Count; i++)
{
var target = _patrolPoints[_patrolIndex];
_patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count;
if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f)
{
_navigationAgent.TargetPosition = target;
return;
}
}
_navigationAgent.TargetPosition = _patrolPoints[_patrolIndex];
}
private void OnVelocityComputed(Vector2 safeVelocity)
{
// 使用安全速度移动,避免与其它角色硬碰硬卡住
Velocity = safeVelocity;
MoveAndSlide();
UpdateFacingAnimation(Velocity);
UpdateStuckTimer(_lastDelta);
}
private void UpdateStuckTimer(double delta)
{
// 若短时间内几乎没有位移,则换一个目标点脱困
if (GlobalPosition.DistanceTo(_lastPosition) <= StuckDistanceEpsilon)
_stuckTimer += (float)delta;
else
_stuckTimer = 0.0f;
_lastPosition = GlobalPosition;
if (_stuckTimer >= StuckRepathSeconds)
{
_stuckTimer = 0.0f;
AdvanceTarget();
}
}
private void UpdateFacingAnimation(Vector2 velocity)
{
if (velocity.LengthSquared() < 0.01f)
{
PlayIdleAnimation();
return;
}
if (Mathf.Abs(velocity.X) >= Mathf.Abs(velocity.Y))
_lastFacing = velocity.X >= 0 ? FacingDirection.Right : FacingDirection.Left;
else
_lastFacing = velocity.Y >= 0 ? FacingDirection.Down : FacingDirection.Up;
switch (_lastFacing)
{
case FacingDirection.Left:
PlayAnimation("walk_left");
break;
case FacingDirection.Right:
PlayAnimation("walk_right");
break;
case FacingDirection.Up:
PlayAnimation("walk_up");
break;
case FacingDirection.Down:
PlayAnimation("walk_down");
break;
}
}
private void PlayIdleAnimation()
{
switch (_lastFacing)
{
case FacingDirection.Left:
PlayAnimation("idle_left");
break;
case FacingDirection.Right:
PlayAnimation("idle_right");
break;
case FacingDirection.Up:
PlayAnimation("idle_back");
break;
case FacingDirection.Down:
PlayAnimation("idle_front");
break;
}
}
private void PlayAnimation(string animationName)
{
if (_animationPlayer == null) return;
if (_animationPlayer.CurrentAnimation != animationName) _animationPlayer.Play(animationName);
}
private enum FacingDirection
{
Up,
Down,
Left,
Right
}
}

View File

@ -0,0 +1 @@
uid://d2m5r9k3n7q1s

View File

@ -86,7 +86,7 @@ public partial class Cube : StaticBody2D, ITileDraggable
} }
private readonly Guid _id = Guid.NewGuid(); private readonly Guid _id = Guid.NewGuid();
public Guid ID => _id; public Guid Id => _id;
public Vector2I TilePosition { get; set; } = new Vector2I(5,5); public Vector2I TilePosition { get; set; } = new Vector2I(5,5);
private bool _isCollided = true; private bool _isCollided = true;
@ -102,19 +102,19 @@ public partial class Cube : StaticBody2D, ITileDraggable
} }
} }
} }
private static readonly Rect2I _tileRect = new(-1, -2, 4, 5); private static readonly Rect2I tileRect = new(-1, -2, 4, 5);
public Rect2I TileRect => _tileRect; public Rect2I TileRect => tileRect;
private Vector2I _mouseOffset; private Vector2I _mouseOffset;
public Vector2I MouseOffset => _mouseOffset; public Vector2I MouseOffset => _mouseOffset;
private static readonly ITileDraggable.SpecialTile[] _specialTiles = { private static readonly ITileDraggable.SpecialTile[] specialTiles = {
new(new Vector2I(1,-1), Lab.MapNodeType.SeatDown | Lab.MapNodeType.Walkable), new(new Vector2I(1,-1), Lab.MapNodeType.SeatDown | Lab.MapNodeType.Walkable),
new(new Vector2I(1,2), Lab.MapNodeType.SeatUp | Lab.MapNodeType.Walkable), new(new Vector2I(1,2), Lab.MapNodeType.SeatUp | Lab.MapNodeType.Walkable),
}; };
public ITileDraggable.SpecialTile[] SpecialTiles => _specialTiles; public ITileDraggable.SpecialTile[] SpecialTiles => specialTiles;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready()
@ -147,27 +147,27 @@ public partial class Cube : StaticBody2D, ITileDraggable
GlobalPosition = TilePosition * 48; GlobalPosition = TilePosition * 48;
} }
private int tableThemeIdx; private int _tableThemeIdx;
private int table2ThemeIdx; private int _table2ThemeIdx;
private int chairThemeIdx; private int _chairThemeIdx;
private int chair2ThemeIdx; private int _chair2ThemeIdx;
private int equipThemeIdx; private int _equipThemeIdx;
private int equip2ThemeIdx; private int _equip2ThemeIdx;
public void RandomChangeTheme() { public void RandomChangeTheme() {
tableThemeIdx = GD.RandRange(0, tableThemes.Length-1); _tableThemeIdx = GD.RandRange(0, tableThemes.Length-1);
chairThemeIdx = GD.RandRange(0, chairThemes.Length-1); _chairThemeIdx = GD.RandRange(0, chairThemes.Length-1);
equipThemeIdx = GD.RandRange(0, equipThemes.Length-1); _equipThemeIdx = GD.RandRange(0, equipThemes.Length-1);
table2ThemeIdx = GD.RandRange(0, table2Themes.Length-1); _table2ThemeIdx = GD.RandRange(0, table2Themes.Length-1);
chair2ThemeIdx = GD.RandRange(0, chair2Themes.Length-1); _chair2ThemeIdx = GD.RandRange(0, chair2Themes.Length-1);
equip2ThemeIdx = GD.RandRange(0, equip2Themes.Length-1); _equip2ThemeIdx = GD.RandRange(0, equip2Themes.Length-1);
tableThemes[tableThemeIdx].Apply(GetNode<TileMapLayer>("Desk")); tableThemes[_tableThemeIdx].Apply(GetNode<TileMapLayer>("Desk"));
chairThemes[chairThemeIdx].Apply(GetNode<TileMapLayer>("Chair")); chairThemes[_chairThemeIdx].Apply(GetNode<TileMapLayer>("Chair"));
equipThemes[equipThemeIdx].Apply(GetNode<TileMapLayer>("Equip")); equipThemes[_equipThemeIdx].Apply(GetNode<TileMapLayer>("Equip"));
table2Themes[table2ThemeIdx].Apply(GetNode<TileMapLayer>("Desk2")); table2Themes[_table2ThemeIdx].Apply(GetNode<TileMapLayer>("Desk2"));
chair2Themes[chair2ThemeIdx].Apply(GetNode<TileMapLayer>("Chair2")); chair2Themes[_chair2ThemeIdx].Apply(GetNode<TileMapLayer>("Chair2"));
equip2Themes[equip2ThemeIdx].Apply(GetNode<TileMapLayer>("Equip2")); equip2Themes[_equip2ThemeIdx].Apply(GetNode<TileMapLayer>("Equip2"));
} }
private static readonly HashSet<Vector2I> blockerTiles = new(new Vector2I[]{ private static readonly HashSet<Vector2I> blockerTiles = new(new Vector2I[]{
new(0,0), new(0,0),
@ -183,15 +183,15 @@ public partial class Cube : StaticBody2D, ITileDraggable
if (blockerTiles.Contains(pos)) { if (blockerTiles.Contains(pos)) {
return Lab.MapNodeType.Blocker; return Lab.MapNodeType.Blocker;
} }
if (_specialTiles.Any(t => t.Position == pos)) { if (specialTiles.Any(t => t.Position == pos)) {
return _specialTiles.First(t => t.Position == pos).NodeType; return specialTiles.First(t => t.Position == pos).NodeType;
} }
return Lab.MapNodeType.Walkable; return Lab.MapNodeType.Walkable;
} }
public void ChairFaceTo(Vector2I target, int idx) { public void ChairFaceTo(Vector2I target, int idx) {
if (idx == 0) { if (idx == 0) {
var theme = chairThemes[chairThemeIdx]; var theme = chairThemes[_chairThemeIdx];
} }
} }

View File

View File

@ -0,0 +1 @@
uid://odcll7t6ojce

View File

@ -10,7 +10,7 @@ public partial class GameManager : Node
/// </summary> /// </summary>
public static bool IsTutorial { get; private set; } public static bool IsTutorial { get; private set; }
public static string NextScene { get; set; } = null; public static string NextScene { get; set; } = null;
public static readonly Resource Arrow2x = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png"); public static readonly Resource Arrow2X = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png");
// --- Core Loop Definitions --- // --- Core Loop Definitions ---
public enum GamePhase public enum GamePhase
@ -46,7 +46,7 @@ public partial class GameManager : Node
public override void _Ready() public override void _Ready()
{ {
Input.SetCustomMouseCursor(Arrow2x); Input.SetCustomMouseCursor(Arrow2X);
// MVP Initialization // MVP Initialization
InitializeGame(); InitializeGame();

View File

@ -23,5 +23,5 @@ public interface ITileDraggable {
} }
Lab.MapNodeType GetTileType(Vector2I pos); Lab.MapNodeType GetTileType(Vector2I pos);
Guid ID { get; } Guid Id { get; }
} }

View File

@ -22,8 +22,8 @@ public partial class Lab : Node2D
new(39,2,1,18), new(39,2,1,18),
}; };
private readonly Dictionary<Guid, ITileDraggable> furnitureIDs = new(); private readonly Dictionary<Guid, ITileDraggable> _furnitureIDs = new();
public Dictionary<Guid, ITileDraggable> Furniture => furnitureIDs; public Dictionary<Guid, ITileDraggable> Furniture => _furnitureIDs;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready()
@ -38,24 +38,24 @@ public partial class Lab : Node2D
Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString(); Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString();
var table = GetNode<Cube>("Cube"); var table = GetNode<Cube>("Cube");
table.Draggable = true; table.Draggable = true;
MoneyLabel = GetNode<Label>("BottomBar/HBoxContainer/Money"); _moneyLabel = GetNode<Label>("BottomBar/HBoxContainer/Money");
var rect = table.TileRect; var rect = table.TileRect;
rect.Position += table.TilePosition; rect.Position += table.TilePosition;
furniturePlacement.Add(rect, table); _furniturePlacement.Add(rect, table);
furnitureIDs.Add(table.ID, table); _furnitureIDs.Add(table.Id, table);
tileMap = GetNode<TileMapLayer>("OutGround"); _tileMap = GetNode<TileMapLayer>("OutGround");
UpdateMap(); UpdateMap();
var student = GetNode<Student>("Student"); var student = GetNode<Student>("Student");
student.CubeId = table.ID; student.CubeId = table.Id;
var r = new Rect2I(0,0,1,1); var r = new Rect2I(0,0,1,1);
GD.Print(H.RectHasPointInclusive(r, new Vector2I(1,0))); GD.Print(H.RectHasPointInclusive(r, new Vector2I(1,0)));
} }
private Label MoneyLabel = null; private Label _moneyLabel = null;
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) public override void _Process(double delta)
{ {
@ -70,7 +70,7 @@ public partial class Lab : Node2D
Player.Budget.Labor += 100; Player.Budget.Labor += 100;
break; break;
} }
MoneyLabel.Text = Player.Budget.Total.ToString("N0"); _moneyLabel.Text = Player.Budget.Total.ToString("N0");
if (_isDragging && DraggingTarget != null) { if (_isDragging && DraggingTarget != null) {
Vector2 mousePos = GetLocalMousePosition(); Vector2 mousePos = GetLocalMousePosition();
Vector2I cell = Point2Coord(mousePos) - DraggingTarget.MouseOffset; Vector2I cell = Point2Coord(mousePos) - DraggingTarget.MouseOffset;
@ -91,42 +91,42 @@ public partial class Lab : Node2D
public void PutDown(ITileDraggable target) { public void PutDown(ITileDraggable target) {
if (target == null) return; if (target == null) return;
_isDragging = false; _isDragging = false;
furniturePlacement.Remove(furniturePlacement.First(kv => kv.Value == target).Key); _furniturePlacement.Remove(_furniturePlacement.First(kv => kv.Value == target).Key);
var rect = target.TileRect; var rect = target.TileRect;
rect.Position += target.TilePosition; rect.Position += target.TilePosition;
furniturePlacement.Add(rect, target); _furniturePlacement.Add(rect, target);
DraggingTarget = null; DraggingTarget = null;
UpdateMap(); UpdateMap();
} }
public ITileDraggable DraggingTarget { get; set; } = null; public ITileDraggable DraggingTarget { get; set; } = null;
public const int MAP_WIDTH = 40; public const int MapWidth = 40;
public const int MAP_HEIGHT = 21; public const int MapHeight = 21;
private readonly MapNodeType[,] blocks = new MapNodeType[MAP_WIDTH, MAP_HEIGHT]; private readonly MapNodeType[,] _blocks = new MapNodeType[MapWidth, MapHeight];
private Dictionary<Rect2I, ITileDraggable> furniturePlacement = new (); private Dictionary<Rect2I, ITileDraggable> _furniturePlacement = new ();
private TileMapLayer tileMap; private TileMapLayer _tileMap;
public void UpdateMap() public void UpdateMap()
{ {
for (int i = 0; i < MAP_WIDTH; i++) { for (int i = 0; i < MapWidth; i++) {
for (int j = 0; j < MAP_HEIGHT; j++) { for (int j = 0; j < MapHeight; j++) {
Vector2I vec = new (i,j); Vector2I vec = new (i,j);
if (wallRectangles.Any(w => w.HasPoint(vec))) { if (wallRectangles.Any(w => w.HasPoint(vec))) {
blocks[i, j] = MapNodeType.Wall; _blocks[i, j] = MapNodeType.Wall;
} else if (furniturePlacement.Any(f => f.Key.HasPoint(vec))) { } else if (_furniturePlacement.Any(f => f.Key.HasPoint(vec))) {
MapNodeType t = 0; MapNodeType t = 0;
foreach (var kv in furniturePlacement.Where(f => f.Key.HasPoint(vec))) { foreach (var kv in _furniturePlacement.Where(f => f.Key.HasPoint(vec))) {
t |= kv.Value.GetTileType(vec - kv.Value.TilePosition); t |= kv.Value.GetTileType(vec - kv.Value.TilePosition);
} }
if ((t & MapNodeType.Walkable) != 0 && (t & MapNodeType.Blocker) != 0){ if ((t & MapNodeType.Walkable) != 0 && (t & MapNodeType.Blocker) != 0){
t ^= MapNodeType.Walkable; t ^= MapNodeType.Walkable;
} }
blocks[i ,j] = t; _blocks[i ,j] = t;
} else { } else {
blocks[i, j] = MapNodeType.Walkable; _blocks[i, j] = MapNodeType.Walkable;
} }
} }
} }
@ -138,7 +138,7 @@ public partial class Lab : Node2D
{ {
int x = pos.X; int x = pos.X;
int y = pos.Y; int y = pos.Y;
if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) { if (x < 0 || x >= MapWidth || y < 0 || y >= MapHeight) {
return false; return false;
} }
return true; return true;
@ -151,16 +151,16 @@ public partial class Lab : Node2D
return neighbor; return neighbor;
} }
if (IsValidPosition(new Vector2I(x-1,y)) && (blocks[x-1,y] & MapNodeType.Walkable) == MapNodeType.Walkable) { if (IsValidPosition(new Vector2I(x-1,y)) && (_blocks[x-1,y] & MapNodeType.Walkable) == MapNodeType.Walkable) {
neighbor.Add(new Vector2I(x-1,y)); neighbor.Add(new Vector2I(x-1,y));
} }
if (IsValidPosition(new Vector2I(x+1,y)) && (blocks[x+1,y] & MapNodeType.Walkable) == MapNodeType.Walkable) { if (IsValidPosition(new Vector2I(x+1,y)) && (_blocks[x+1,y] & MapNodeType.Walkable) == MapNodeType.Walkable) {
neighbor.Add(new Vector2I(x+1,y)); neighbor.Add(new Vector2I(x+1,y));
} }
if (IsValidPosition(new Vector2I(x,y-1)) && (blocks[x,y-1] & MapNodeType.Walkable) == MapNodeType.Walkable) { if (IsValidPosition(new Vector2I(x,y-1)) && (_blocks[x,y-1] & MapNodeType.Walkable) == MapNodeType.Walkable) {
neighbor.Add(new Vector2I(x,y-1)); neighbor.Add(new Vector2I(x,y-1));
} }
if (IsValidPosition(new Vector2I(x,y+1)) && (blocks[x,y+1] & MapNodeType.Walkable) == MapNodeType.Walkable) { if (IsValidPosition(new Vector2I(x,y+1)) && (_blocks[x,y+1] & MapNodeType.Walkable) == MapNodeType.Walkable) {
neighbor.Add(new Vector2I(x,y+1)); neighbor.Add(new Vector2I(x,y+1));
} }
return neighbor; return neighbor;
@ -168,57 +168,57 @@ public partial class Lab : Node2D
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end) public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
{ {
for (int j = 0; j < MAP_HEIGHT; j++) { for (int j = 0; j < MapHeight; j++) {
string t = ""; string t = "";
for (int i = 0; i < MAP_WIDTH; i++) { for (int i = 0; i < MapWidth; i++) {
if ((blocks[i,j] & MapNodeType.SeatUp) != 0) { if ((_blocks[i,j] & MapNodeType.SeatUp) != 0) {
t += "U"; t += "U";
} else if ((blocks[i,j] & MapNodeType.SeatDown) != 0) { } else if ((_blocks[i,j] & MapNodeType.SeatDown) != 0) {
t += "D"; t += "D";
} else { } else {
t += $"{blocks[i,j].ToString()[0]}"; t += $"{_blocks[i,j].ToString()[0]}";
} }
} }
GD.Print(t); GD.Print(t);
} }
var path = new List<Vector2I>(); var path = new List<Vector2I>();
if (tileMap.GetCellSourceId(start) == -1 || tileMap.GetCellSourceId(end) == -1) { if (_tileMap.GetCellSourceId(start) == -1 || _tileMap.GetCellSourceId(end) == -1) {
return path; return path;
} }
if (start == end) { if (start == end) {
return path; return path;
} }
GD.Print($"start = {start}, end = {end}"); GD.Print($"start = {start}, end = {end}");
static int vec2idx(Vector2I v) => v.Y * MAP_WIDTH + v.X; static int Vec2Idx(Vector2I v) => v.Y * MapWidth + v.X;
static Vector2I idx2vec2(int i) => new(i % MAP_WIDTH, i / MAP_WIDTH); static Vector2I Idx2Vec2(int i) => new(i % MapWidth, i / MapWidth);
HashSet<int> entriesIdx = new(); HashSet<int> entriesIdx = new();
foreach (var e in GetNeighbors(end)) { foreach (var e in GetNeighbors(end)) {
entriesIdx.Add(vec2idx(e)); entriesIdx.Add(Vec2Idx(e));
} }
if (entriesIdx.Count == 0) { if (entriesIdx.Count == 0) {
return path; return path;
} }
// Use Dijkstra algorithm to find the shortest path // Use Dijkstra algorithm to find the shortest path
int[] dist = new int[MAP_WIDTH * MAP_HEIGHT]; int[] dist = new int[MapWidth * MapHeight];
int[] prev = new int[MAP_WIDTH * MAP_HEIGHT]; int[] prev = new int[MapWidth * MapHeight];
bool[] visited = new bool[MAP_WIDTH * MAP_HEIGHT]; bool[] visited = new bool[MapWidth * MapHeight];
for (int i = 0; i < dist.Length; i++) { for (int i = 0; i < dist.Length; i++) {
dist[i] = int.MaxValue; dist[i] = int.MaxValue;
prev[i] = -1; prev[i] = -1;
visited[i] = false; visited[i] = false;
} }
dist[vec2idx(start)] = 0; dist[Vec2Idx(start)] = 0;
int answer = -1; int answer = -1;
for (int i = 0; i < dist.Length; i++) { for (int i = 0; i < dist.Length; i++) {
int min_distance = int.MaxValue; int minDistance = int.MaxValue;
int u = -1; int u = -1;
for (int j = 0; j < dist.Length; j++) { for (int j = 0; j < dist.Length; j++) {
if (!visited[j] && dist[j] <= min_distance) { if (!visited[j] && dist[j] <= minDistance) {
min_distance = dist[j]; minDistance = dist[j];
u = j; u = j;
} }
} }
@ -230,10 +230,10 @@ public partial class Lab : Node2D
break; break;
} }
visited[u] = true; visited[u] = true;
var neighbors = GetNeighbors(idx2vec2(u)); var neighbors = GetNeighbors(Idx2Vec2(u));
int alt = dist[u] + 1; int alt = dist[u] + 1;
foreach (var neighbor in neighbors) { foreach (var neighbor in neighbors) {
int idx = vec2idx(neighbor); int idx = Vec2Idx(neighbor);
if (visited[idx]) continue; if (visited[idx]) continue;
if (alt < dist[idx]) { if (alt < dist[idx]) {
dist[idx] = alt; dist[idx] = alt;
@ -242,8 +242,8 @@ public partial class Lab : Node2D
} }
} }
while (answer != vec2idx(start)) { while (answer != Vec2Idx(start)) {
path.Add(idx2vec2(answer)); path.Add(Idx2Vec2(answer));
answer = prev[answer]; answer = prev[answer];
} }
path.Reverse(); path.Reverse();
@ -260,17 +260,17 @@ public partial class Lab : Node2D
public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) { public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) {
if (!IsValidPosition(pos)) return MapNodeType.Invalid; if (!IsValidPosition(pos)) return MapNodeType.Invalid;
return blocks[pos.X, pos.Y]; return _blocks[pos.X, pos.Y];
} }
public Vector2I Point2Coord(Vector2 pos) { public Vector2I Point2Coord(Vector2 pos) {
return tileMap.LocalToMap(tileMap.ToLocal(pos)); return _tileMap.LocalToMap(_tileMap.ToLocal(pos));
} }
public Vector2I GetFurSpecialPosition(Guid fID, int idx) { public Vector2I GetFurSpecialPosition(Guid fId, int idx) {
if (!furnitureIDs.ContainsKey(fID)) return Vector2I.Zero; if (!_furnitureIDs.ContainsKey(fId)) return Vector2I.Zero;
if (idx < 0 || idx > furnitureIDs[fID].SpecialTiles.Length) return Vector2I.Zero; if (idx < 0 || idx > _furnitureIDs[fId].SpecialTiles.Length) return Vector2I.Zero;
return furnitureIDs[fID].SpecialTiles[idx].Position + furnitureIDs[fID].TilePosition; return _furnitureIDs[fId].SpecialTiles[idx].Position + _furnitureIDs[fId].TilePosition;
} }
} }

View File

@ -4,9 +4,9 @@ using System.Collections.Generic;
public partial class Loader : Control public partial class Loader : Control
{ {
private ProgressBar progressBar; private ProgressBar _progressBar;
private int sceneLoaded = 0; private int _sceneLoaded = 0;
private List<string> resToLoad = new (){ private List<string> _resToLoad = new (){
"res://scenes/lab.tscn", "res://scenes/lab.tscn",
"res://scenes/player.tscn" "res://scenes/player.tscn"
}; };
@ -14,7 +14,7 @@ public partial class Loader : Control
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready()
{ {
progressBar = GetNode<ProgressBar>("ProgressBar"); _progressBar = GetNode<ProgressBar>("ProgressBar");
string nextScene = GameManager.NextScene; string nextScene = GameManager.NextScene;
if (nextScene != null) { if (nextScene != null) {
GameManager.NextScene = "res://scenes/lab.tscn"; GameManager.NextScene = "res://scenes/lab.tscn";
@ -25,8 +25,8 @@ public partial class Loader : Control
// resToLoad.AddRange(Res.Hair); // resToLoad.AddRange(Res.Hair);
// resToLoad.AddRange(Res.Outfit); // resToLoad.AddRange(Res.Outfit);
// resToLoad.AddRange(Res.Smartphone); // resToLoad.AddRange(Res.Smartphone);
ResourceLoader.LoadThreadedRequest(resToLoad[sceneLoaded]); ResourceLoader.LoadThreadedRequest(_resToLoad[_sceneLoaded]);
progressBar.MaxValue = 100 * resToLoad.Count; _progressBar.MaxValue = 100 * _resToLoad.Count;
SetProcess(true); SetProcess(true);
} }
@ -34,22 +34,22 @@ public partial class Loader : Control
public override void _Process(double delta) public override void _Process(double delta)
{ {
Godot.Collections.Array progress = new(); Godot.Collections.Array progress = new();
switch(ResourceLoader.LoadThreadedGetStatus(resToLoad[sceneLoaded], progress)) switch(ResourceLoader.LoadThreadedGetStatus(_resToLoad[_sceneLoaded], progress))
{ {
case ResourceLoader.ThreadLoadStatus.InProgress: case ResourceLoader.ThreadLoadStatus.InProgress:
progressBar.Value = 100 * sceneLoaded + (int)((float)progress[0] * 100); _progressBar.Value = 100 * _sceneLoaded + (int)((float)progress[0] * 100);
break; break;
case ResourceLoader.ThreadLoadStatus.Loaded: case ResourceLoader.ThreadLoadStatus.Loaded:
sceneLoaded++; _sceneLoaded++;
if(sceneLoaded < resToLoad.Count) { if(_sceneLoaded < _resToLoad.Count) {
ResourceLoader.LoadThreadedRequest(resToLoad[sceneLoaded]); ResourceLoader.LoadThreadedRequest(_resToLoad[_sceneLoaded]);
} else { } else {
SetProcess(false); SetProcess(false);
GetTree().ChangeSceneToFile("res://scenes/lab.tscn"); GetTree().ChangeSceneToFile("res://scenes/lab.tscn");
} }
break; break;
default: default:
GD.Print("Error loading scene: " + resToLoad[sceneLoaded]); GD.Print("Error loading scene: " + _resToLoad[_sceneLoaded]);
break; break;
} }
} }

View File

@ -43,18 +43,18 @@ public partial class Player : Node
private Dictionary<DateOnly, HashSet<Guid>> _events; private Dictionary<DateOnly, HashSet<Guid>> _events;
public void Subscribe(DateOnly date, Guid eventUUID) public void Subscribe(DateOnly date, Guid eventUuid)
{ {
_events ??= new Dictionary<DateOnly, HashSet<Guid>>(); _events ??= new Dictionary<DateOnly, HashSet<Guid>>();
if (!_events.ContainsKey(date)) _events[date] = new HashSet<Guid>(); if (!_events.ContainsKey(date)) _events[date] = new HashSet<Guid>();
_events[date].Add(eventUUID); _events[date].Add(eventUuid);
} }
public void Unsubscribe(DateOnly date, Guid eventUUID) public void Unsubscribe(DateOnly date, Guid eventUuid)
{ {
if (_events == null) return; if (_events == null) return;
if (!_events.ContainsKey(date)) return; if (!_events.ContainsKey(date)) return;
_events[date].Remove(eventUUID); _events[date].Remove(eventUuid);
} }
private void NextDay() private void NextDay()
@ -69,11 +69,11 @@ public partial class Player : Node
if (_events.ContainsKey(InternalDate)) if (_events.ContainsKey(InternalDate))
{ {
var events = _events[InternalDate]; var events = _events[InternalDate];
foreach (var eventUUID in events) foreach (var eventUuid in events)
{ {
// TODO: Trigger event // TODO: Trigger event
OnEventTriggered?.Invoke(InternalDate, eventUUID); OnEventTriggered?.Invoke(InternalDate, eventUuid);
GD.Print("event triggered! eventUUID: " + eventUUID.ToString()); GD.Print("event triggered! eventUUID: " + eventUuid.ToString());
} }
} }
} }
@ -88,7 +88,7 @@ public partial class Player : Node
InternalDate = startDate; InternalDate = startDate;
} }
public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUUID); public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUuid);
public event EventTriggeredEventHandler OnEventTriggered; public event EventTriggeredEventHandler OnEventTriggered;

View File

@ -15,12 +15,12 @@ public partial class Res : Node
public enum Type public enum Type
{ {
ACCESSORY, Accessory,
BODY, Body,
EYE, Eye,
HAIR, Hair,
OUTFIT, Outfit,
PHONE Phone
} }
public static string GetRandom(Type resType) public static string GetRandom(Type resType)
@ -33,7 +33,7 @@ public partial class Res : Node
return path.Replace("_48x48_", "_").Replace("_48x48", ""); return path.Replace("_48x48_", "_").Replace("_48x48", "");
} }
public static string GetRandom(Type resType, bool use16x16Sprites) public static string GetRandom(Type resType, bool use16X16Sprites)
{ {
var resources = allResources[(int)resType]; var resources = allResources[(int)resType];
if (resources == null) { if (resources == null) {
@ -42,7 +42,7 @@ public partial class Res : Node
} }
int index = GD.RandRange(0, resources.Length - 1); int index = GD.RandRange(0, resources.Length - 1);
var path = resources[index]; var path = resources[index];
if (!use16x16Sprites) return path; if (!use16X16Sprites) return path;
var path16 = To16Path(path); var path16 = To16Path(path);
return ResourceLoader.Exists(path16) ? path16 : path; return ResourceLoader.Exists(path16) ? path16 : path;

View File

@ -9,7 +9,7 @@ public partial class Student : CharacterBody2D
{ {
public float Speed { get; set; } = 8.0f; public float Speed { get; set; } = 8.0f;
public const float JumpVelocity = -400.0f; public const float JumpVelocity = -400.0f;
[Export] public bool Use16x16Sprites { get; set; } = false; [Export] public bool Use16X16Sprites { get; set; } = false;
// --- MVP: Model Binding --- // --- MVP: Model Binding ---
public StudentModel Model { get; private set; } public StudentModel Model { get; private set; }
@ -23,8 +23,8 @@ public partial class Student : CharacterBody2D
public int NextType = -1; public int NextType = -1;
private Queue<Vector2I> PathToGo = new(); private Queue<Vector2I> _pathToGo = new();
private AnimationPlayer animationPlayer; private AnimationPlayer _animationPlayer;
public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp } public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp }
public CharacterState State { get; set; } = CharacterState.Idle; public CharacterState State { get; set; } = CharacterState.Idle;
public enum Direction { Up, Down, Left, Right } public enum Direction { Up, Down, Left, Right }
@ -32,27 +32,27 @@ public partial class Student : CharacterBody2D
public Queue<CharacterState> StateQueue { get; set; } = new(); public Queue<CharacterState> StateQueue { get; set; } = new();
private Vector2I targetSpecialPosition; private Vector2I _targetSpecialPosition;
private Vector2I lastWalkablePosition; private Vector2I _lastWalkablePosition;
private double duration = 0; private double _duration = 0;
private void SitDown(double delta) { private void SitDown(double delta) {
duration += delta; _duration += delta;
if (!(duration > 0.3)) return; if (!(_duration > 0.3)) return;
if (TargetDirection == Direction.Up) { if (TargetDirection == Direction.Up) {
} }
animationPlayer.Play("idle_front"); _animationPlayer.Play("idle_front");
State = StateQueue.Dequeue(); State = StateQueue.Dequeue();
GlobalPosition = targetSpecialPosition; GlobalPosition = _targetSpecialPosition;
} }
private void GettingUp(double delta) { private void GettingUp(double delta) {
duration += delta; _duration += delta;
if (!(duration > 0.3)) return; if (!(_duration > 0.3)) return;
State = StateQueue.Dequeue(); State = StateQueue.Dequeue();
GlobalPosition = lastWalkablePosition; GlobalPosition = _lastWalkablePosition;
} }
public override void _PhysicsProcess(double delta) public override void _PhysicsProcess(double delta)
{ {
@ -64,7 +64,7 @@ public partial class Student : CharacterBody2D
} }
if (StateQueue.Peek() == CharacterState.StandingUp && State == CharacterState.Sitting) { if (StateQueue.Peek() == CharacterState.StandingUp && State == CharacterState.Sitting) {
duration = 0; _duration = 0;
State = StateQueue.Dequeue(); State = StateQueue.Dequeue();
return; return;
} }
@ -77,17 +77,17 @@ public partial class Student : CharacterBody2D
// GD.Print($"{State} -> Empty"); // GD.Print($"{State} -> Empty");
} }
if (PathToGo.Count == 0) if (_pathToGo.Count == 0)
{ {
if (State != CharacterState.Walking) return; if (State != CharacterState.Walking) return;
if (StateQueue.Count > 0) { if (StateQueue.Count > 0) {
if (StateQueue.Peek() == CharacterState.SittingDown) { if (StateQueue.Peek() == CharacterState.SittingDown) {
duration = 0; _duration = 0;
} }
State = StateQueue.Dequeue(); State = StateQueue.Dequeue();
} }
if (State == CharacterState.Idle) { if (State == CharacterState.Idle) {
animationPlayer.Play("idle_front"); _animationPlayer.Play("idle_front");
} }
return;// No path to follow. return;// No path to follow.
} }
@ -95,10 +95,10 @@ public partial class Student : CharacterBody2D
if (State != CharacterState.Walking) return; if (State != CharacterState.Walking) return;
Vector2 velocity = new(); Vector2 velocity = new();
var nextPoint = PathToGo.Peek(); var nextPoint = _pathToGo.Peek();
if ((int)GlobalPosition.X == nextPoint.X && (int)GlobalPosition.Y == nextPoint.Y) if ((int)GlobalPosition.X == nextPoint.X && (int)GlobalPosition.Y == nextPoint.Y)
{ {
PathToGo.Dequeue(); _pathToGo.Dequeue();
return; return;
} }
@ -109,10 +109,10 @@ public partial class Student : CharacterBody2D
velocity.Y = Speed; velocity.Y = Speed;
if (GlobalPosition.Y > nextPoint.Y) if (GlobalPosition.Y > nextPoint.Y)
{ {
animationPlayer.Play("walk_up"); _animationPlayer.Play("walk_up");
velocity.Y = -velocity.Y; velocity.Y = -velocity.Y;
} else { } else {
animationPlayer.Play("walk_down"); _animationPlayer.Play("walk_down");
} }
} }
else if ((int)GlobalPosition.Y == nextPoint.Y) else if ((int)GlobalPosition.Y == nextPoint.Y)
@ -123,10 +123,10 @@ public partial class Student : CharacterBody2D
if (GlobalPosition.X > nextPoint.X) if (GlobalPosition.X > nextPoint.X)
{ {
animationPlayer.Play("walk_left"); _animationPlayer.Play("walk_left");
velocity.X = -velocity.X; velocity.X = -velocity.X;
} else { } else {
animationPlayer.Play("walk_right"); _animationPlayer.Play("walk_right");
} }
} }
GlobalPosition = GlobalPosition with GlobalPosition = GlobalPosition with
@ -149,17 +149,17 @@ public partial class Student : CharacterBody2D
// bb.BindVarToProperty("Stayed", this, "Speed"); // bb.BindVarToProperty("Stayed", this, "Speed");
// GD.Print(bb.GetVar("Stayed")); // GD.Print(bb.GetVar("Stayed"));
// GD.Print($"Speed: {Speed}"); // GD.Print($"Speed: {Speed}");
animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer"); _animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
animationPlayer.Play("idle_front"); _animationPlayer.Play("idle_front");
var name_test = GetNode<StudentName>("StudentName"); var nameTest = GetNode<StudentName>("StudentName");
// MVP: Initialize Model if null // MVP: Initialize Model if null
if (Model == null) if (Model == null)
{ {
Model = new StudentModel(name_test.GenerateName()); Model = new StudentModel(nameTest.GenerateName());
GD.Print($"[Auto-Init] Student: {Model.Name}"); GD.Print($"[Auto-Init] Student: {Model.Name}");
} else { } else {
GD.Print("生成的名字是: " + name_test.GenerateName()); // Keep original log for reference GD.Print("生成的名字是: " + nameTest.GenerateName()); // Keep original log for reference
} }
} }
@ -167,7 +167,7 @@ public partial class Student : CharacterBody2D
{ {
foreach (var p in path) foreach (var p in path)
{ {
PathToGo.Enqueue(new Vector2I(p.X * 48 + 24, p.Y * 48 + 24)); _pathToGo.Enqueue(new Vector2I(p.X * 48 + 24, p.Y * 48 + 24));
} }
} }
@ -185,20 +185,20 @@ public partial class Student : CharacterBody2D
} }
Vector2I vec = Vector2I.Zero; Vector2I vec = Vector2I.Zero;
while ((lab.GetMapNodeTypeOfPosition(vec) & Lab.MapNodeType.Walkable) == 0) { while ((lab.GetMapNodeTypeOfPosition(vec) & Lab.MapNodeType.Walkable) == 0) {
vec.X = GD.RandRange(0, Lab.MAP_WIDTH); vec.X = GD.RandRange(0, Lab.MapWidth);
vec.Y = GD.RandRange(0, Lab.MAP_HEIGHT); vec.Y = GD.RandRange(0, Lab.MapHeight);
} }
var pos = GlobalPosition; var pos = GlobalPosition;
if (State == CharacterState.Sitting) { if (State == CharacterState.Sitting) {
StateQueue.Enqueue(CharacterState.StandingUp); StateQueue.Enqueue(CharacterState.StandingUp);
StateQueue.Enqueue(CharacterState.Walking); StateQueue.Enqueue(CharacterState.Walking);
pos = lastWalkablePosition; pos = _lastWalkablePosition;
} else if (State == CharacterState.Idle) { } else if (State == CharacterState.Idle) {
State = CharacterState.Walking; State = CharacterState.Walking;
} }
StateQueue.Enqueue(CharacterState.Idle); StateQueue.Enqueue(CharacterState.Idle);
MoveFollowPath(lab.GetShortestPath(lab.Point2Coord(pos), vec)); MoveFollowPath(lab.GetShortestPath(lab.Point2Coord(pos), vec));
randomChangeBody(); RandomChangeBody();
} }
public void GoToSeat() { public void GoToSeat() {
@ -216,23 +216,23 @@ public partial class Student : CharacterBody2D
if (i == 1) { if (i == 1) {
// Seat up // Seat up
TargetDirection = Direction.Up; TargetDirection = Direction.Up;
targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 12); _targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 12);
} else { } else {
TargetDirection = Direction.Down; TargetDirection = Direction.Down;
targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 18); _targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 18);
} }
var pos = GlobalPosition; var pos = GlobalPosition;
if (State == CharacterState.Sitting) { if (State == CharacterState.Sitting) {
StateQueue.Enqueue(CharacterState.StandingUp); StateQueue.Enqueue(CharacterState.StandingUp);
StateQueue.Enqueue(CharacterState.Walking); StateQueue.Enqueue(CharacterState.Walking);
pos = lastWalkablePosition; pos = _lastWalkablePosition;
} else if (State == CharacterState.Idle) { } else if (State == CharacterState.Idle) {
State = CharacterState.Walking; State = CharacterState.Walking;
} }
StateQueue.Enqueue(CharacterState.SittingDown); StateQueue.Enqueue(CharacterState.SittingDown);
StateQueue.Enqueue(CharacterState.Sitting); StateQueue.Enqueue(CharacterState.Sitting);
var path = lab.GetShortestPath(lab.Point2Coord(pos), target); var path = lab.GetShortestPath(lab.Point2Coord(pos), target);
lastWalkablePosition = new Vector2I(path.Last().X * 48 + 24, path.Last().Y * 48 + 24); _lastWalkablePosition = new Vector2I(path.Last().X * 48 + 24, path.Last().Y * 48 + 24);
// if (lastWalkablePosition.X < targetSpecialPosition.X) { // if (lastWalkablePosition.X < targetSpecialPosition.X) {
// TargetDirection = Direction.Right; // TargetDirection = Direction.Right;
// } else if (lastWalkablePosition.X > targetSpecialPosition.X) { // } else if (lastWalkablePosition.X > targetSpecialPosition.X) {
@ -245,16 +245,16 @@ public partial class Student : CharacterBody2D
// } // }
// } // }
MoveFollowPath(path); MoveFollowPath(path);
randomChangeBody(); RandomChangeBody();
} }
private void randomChangeBody() private void RandomChangeBody()
{ {
GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.BODY, Use16x16Sprites)); GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites));
GetNode<Sprite2D>("parts/hairstyle").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.HAIR, Use16x16Sprites)); GetNode<Sprite2D>("parts/hairstyle").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Hair, Use16X16Sprites));
GetNode<Sprite2D>("parts/outfit").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.OUTFIT, Use16x16Sprites)); GetNode<Sprite2D>("parts/outfit").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Outfit, Use16X16Sprites));
GetNode<Sprite2D>("parts/eye").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.EYE, Use16x16Sprites)); GetNode<Sprite2D>("parts/eye").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Eye, Use16X16Sprites));
GetNode<Sprite2D>("parts/accessory").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.ACCESSORY, Use16x16Sprites)); 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)); GetNode<Sprite2D>("parts/smartphone").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Phone, Use16X16Sprites));
} }
} }

View File

@ -7,14 +7,14 @@ public partial class TestMap : TileMapLayer
/// <summary> /// <summary>
/// The last cell that was highlighted. Used to avoid unnecessary updates. /// The last cell that was highlighted. Used to avoid unnecessary updates.
/// </summary> /// </summary>
private Vector2I lastHighlightCell; private Vector2I _lastHighlightCell;
private readonly Vector2I highlightTileCoord = new(0, 5); private readonly Vector2I _highlightTileCoord = new(0, 5);
private readonly Vector2I vector2INegOne = new(-1, -1); 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.
public override void _Ready() public override void _Ready()
{ {
// Initialize the lastHighlightCell to an invalid value. // Initialize the lastHighlightCell to an invalid value.
lastHighlightCell = vector2INegOne; _lastHighlightCell = _vector2INegOne;
} }
/// <summary> /// <summary>
@ -26,14 +26,14 @@ public partial class TestMap : TileMapLayer
Vector2 mousePos = GetLocalMousePosition(); Vector2 mousePos = GetLocalMousePosition();
Vector2I cell = LocalToMap(mousePos); Vector2I cell = LocalToMap(mousePos);
if (cell != lastHighlightCell) if (cell != _lastHighlightCell)
{ {
if (lastHighlightCell != vector2INegOne) if (_lastHighlightCell != _vector2INegOne)
{ {
EraseCell(lastHighlightCell); EraseCell(_lastHighlightCell);
} }
lastHighlightCell = cell; _lastHighlightCell = cell;
SetCell(cell, 0, highlightTileCoord); SetCell(cell, 0, _highlightTileCoord);
} }
} }

View File

@ -8,40 +8,40 @@ public partial class TileMapping : Node
/// <summary> /// <summary>
/// From tile coordinates to pixel coordinates. If null, use the offset instead. /// From tile coordinates to pixel coordinates. If null, use the offset instead.
/// </summary> /// </summary>
private Dictionary<Vector2I, Vector2I> pixelMap; private Dictionary<Vector2I, Vector2I> _pixelMap;
/// <summary> /// <summary>
/// If pixelMap is null, use this offset to convert tile coordinates to pixel coordinates. /// If pixelMap is null, use this offset to convert tile coordinates to pixel coordinates.
/// </summary> /// </summary>
private Vector2I pixelOffset; private Vector2I _pixelOffset;
private Rect2I tileRect; private Rect2I _tileRect;
public TileMapping(Dictionary<Vector2I, Vector2I> map) { public TileMapping(Dictionary<Vector2I, Vector2I> map) {
pixelMap = map; _pixelMap = map;
pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue); _pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue);
} }
public TileMapping(Vector2I offset, Rect2I rect) { public TileMapping(Vector2I offset, Rect2I rect) {
pixelMap = null; _pixelMap = null;
pixelOffset = offset; _pixelOffset = offset;
tileRect = rect; _tileRect = rect;
} }
public TileMapping() {} public TileMapping() {}
public void Apply(TileMapLayer layer) { public void Apply(TileMapLayer layer) {
// if pixelMap is not null, use all the pairs in pixelMap to set the tiles. // if pixelMap is not null, use all the pairs in pixelMap to set the tiles.
if (pixelMap != null) { if (_pixelMap != null) {
var sourceId = layer.GetCellSourceId(pixelMap.Keys.First()); var sourceId = layer.GetCellSourceId(_pixelMap.Keys.First());
foreach (var pair in pixelMap) { foreach (var pair in _pixelMap) {
layer.SetCell(pair.Key, sourceId, pair.Value); layer.SetCell(pair.Key, sourceId, pair.Value);
} }
} else { } else {
var sourceId = layer.GetCellSourceId(tileRect.Position); var sourceId = layer.GetCellSourceId(_tileRect.Position);
GD.Print(tileRect); GD.Print(_tileRect);
for (int x = tileRect.Position.X; x < tileRect.End.X; x++) { for (int x = _tileRect.Position.X; x < _tileRect.End.X; x++) {
for (int y = tileRect.Position.Y; y < tileRect.End.Y; y++) { for (int y = _tileRect.Position.Y; y < _tileRect.End.Y; y++) {
var pos = new Vector2I(x, y); var pos = new Vector2I(x, y);
layer.SetCell(pos, sourceId, pos + pixelOffset); layer.SetCell(pos, sourceId, pos + _pixelOffset);
} }
} }
} }