324 lines
10 KiB
C#
324 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Godot;
|
|
using Models;
|
|
|
|
// ReSharper disable CheckNamespace
|
|
|
|
/// <summary>
|
|
/// 学生角色控制器
|
|
/// </summary>
|
|
public partial class Student : CharacterBody2D {
|
|
/// <summary>
|
|
/// 角色状态枚举
|
|
/// </summary>
|
|
public enum CharacterState {
|
|
Idle,
|
|
Walking,
|
|
Sitting,
|
|
SittingDown,
|
|
StandingUp
|
|
}
|
|
|
|
/// <summary>
|
|
/// 方向枚举
|
|
/// </summary>
|
|
public enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right
|
|
}
|
|
|
|
/// <summary>
|
|
/// 跳跃速度
|
|
/// </summary>
|
|
public const float JumpVelocity = -400.0f;
|
|
|
|
private readonly Queue<Vector2I> _pathToGo = new();
|
|
private AnimationPlayer _animationPlayer;
|
|
private double _duration;
|
|
private Vector2I _lastWalkablePosition;
|
|
|
|
private Vector2I _targetSpecialPosition;
|
|
|
|
/// <summary>
|
|
/// 关联的格子ID
|
|
/// </summary>
|
|
public Guid CubeId;
|
|
// --------------------------
|
|
|
|
/// <summary>
|
|
/// 下一个状态类型(未使用)
|
|
/// </summary>
|
|
public int NextType = -1;
|
|
|
|
/// <summary>
|
|
/// 移动速度
|
|
/// </summary>
|
|
public float Speed { get; set; } = 8.0f;
|
|
|
|
/// <summary>
|
|
/// 是否使用16x16精灵
|
|
/// </summary>
|
|
[Export]
|
|
public bool Use16X16Sprites { get; set; }
|
|
|
|
// --- MVP: Model Binding ---
|
|
/// <summary>
|
|
/// 学生数据模型
|
|
/// </summary>
|
|
public StudentModel Model { get; private set; }
|
|
|
|
/// <summary>
|
|
/// 当前状态
|
|
/// </summary>
|
|
public CharacterState State { get; set; } = CharacterState.Idle;
|
|
|
|
/// <summary>
|
|
/// 目标方向
|
|
/// </summary>
|
|
public Direction TargetDirection { get; set; } = Direction.Up;
|
|
|
|
/// <summary>
|
|
/// 状态队列
|
|
/// </summary>
|
|
public Queue<CharacterState> StateQueue { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// 绑定数据模型
|
|
/// </summary>
|
|
/// <param name="model">学生模型</param>
|
|
public void BindData(StudentModel model) {
|
|
Model = model;
|
|
GD.Print($"Student bound to model: {Model.Name}");
|
|
}
|
|
|
|
|
|
private void SitDown(double delta) {
|
|
_duration += delta;
|
|
if (!(_duration > 0.3)) return;
|
|
if (TargetDirection == Direction.Up) { }
|
|
|
|
_animationPlayer.Play("idle_front");
|
|
State = StateQueue.Dequeue();
|
|
GlobalPosition = _targetSpecialPosition;
|
|
}
|
|
|
|
private void GettingUp(double delta) {
|
|
_duration += delta;
|
|
if (!(_duration > 0.3)) return;
|
|
State = StateQueue.Dequeue();
|
|
GlobalPosition = _lastWalkablePosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 物理处理
|
|
/// </summary>
|
|
/// <param name="delta">时间间隔</param>
|
|
public override void _PhysicsProcess(double delta) {
|
|
if (StateQueue.Count > 0) {
|
|
// GD.Print($"{State} -> {StateQueue.Peek()}");
|
|
if (StateQueue.Peek() == CharacterState.Sitting && State == CharacterState.SittingDown) {
|
|
SitDown(delta);
|
|
return; // do not handle potentially conflicting command
|
|
}
|
|
|
|
if (StateQueue.Peek() == CharacterState.StandingUp && State == CharacterState.Sitting) {
|
|
_duration = 0;
|
|
State = StateQueue.Dequeue();
|
|
return;
|
|
}
|
|
|
|
if (StateQueue.Peek() == CharacterState.Walking && State == CharacterState.StandingUp) {
|
|
GettingUp(delta);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// GD.Print($"{State} -> Empty");
|
|
if (_pathToGo.Count == 0) {
|
|
if (State != CharacterState.Walking) return;
|
|
if (StateQueue.Count > 0) {
|
|
if (StateQueue.Peek() == CharacterState.SittingDown) _duration = 0;
|
|
State = StateQueue.Dequeue();
|
|
}
|
|
|
|
if (State == CharacterState.Idle) _animationPlayer.Play("idle_front");
|
|
return; // No path to follow.
|
|
}
|
|
|
|
if (State != CharacterState.Walking) return;
|
|
|
|
Vector2 velocity = new();
|
|
var nextPoint = _pathToGo.Peek();
|
|
if ((int)GlobalPosition.X == nextPoint.X && (int)GlobalPosition.Y == nextPoint.Y) {
|
|
_pathToGo.Dequeue();
|
|
return;
|
|
}
|
|
|
|
if ((int)GlobalPosition.X == nextPoint.X) {
|
|
// Move Y
|
|
// velocity.Y = Math.Max(Speed, Math.Abs(nextPoint.Y - GlobalPosition.Y));
|
|
velocity.Y = Speed;
|
|
if (GlobalPosition.Y > nextPoint.Y) {
|
|
_animationPlayer.Play("walk_up");
|
|
velocity.Y = -velocity.Y;
|
|
}
|
|
else {
|
|
_animationPlayer.Play("walk_down");
|
|
}
|
|
}
|
|
else if ((int)GlobalPosition.Y == nextPoint.Y) {
|
|
// move X
|
|
// velocity.X = Math.Max(Speed, Math.Abs(nextPoint.X - GlobalPosition.X));
|
|
velocity.X = Speed;
|
|
if (GlobalPosition.X > nextPoint.X) {
|
|
_animationPlayer.Play("walk_left");
|
|
velocity.X = -velocity.X;
|
|
}
|
|
else {
|
|
_animationPlayer.Play("walk_right");
|
|
}
|
|
}
|
|
|
|
GlobalPosition = GlobalPosition with {
|
|
X = GlobalPosition.X + velocity.X,
|
|
Y = GlobalPosition.Y + velocity.Y
|
|
};
|
|
|
|
// GD.Print($"{GlobalPosition} -> {nextPoint}");
|
|
|
|
// Velocity = velocity;
|
|
// MoveAndSlide();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 准备就绪时调用
|
|
/// </summary>
|
|
public override void _Ready() {
|
|
base._Ready();
|
|
// var bt = GetNode<BTPlayer>("BTPlayer");
|
|
// var bb = bt.Blackboard;
|
|
// bb.BindVarToProperty("Stayed", this, "Speed");
|
|
// GD.Print(bb.GetVar("Stayed"));
|
|
// GD.Print($"Speed: {Speed}");
|
|
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
|
|
_animationPlayer.Play("idle_front");
|
|
var nameTest = GetNode<StudentName>("StudentName");
|
|
|
|
// MVP: Initialize Model if null
|
|
if (Model == null) {
|
|
Model = new StudentModel(nameTest.GenerateName());
|
|
GD.Print($"[Auto-Init] Student: {Model.Name}");
|
|
}
|
|
else {
|
|
GD.Print("生成的名字是: " + nameTest.GenerateName()); // Keep original log for reference
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 移动跟随路径
|
|
/// </summary>
|
|
/// <param name="path">路径点列表</param>
|
|
public void MoveFollowPath(List<Vector2I> path) {
|
|
foreach (var p in path) _pathToGo.Enqueue(new Vector2I(p.X * 48 + 24, p.Y * 48 + 24));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 随机前往某处
|
|
/// </summary>
|
|
public void GoTo() {
|
|
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp ||
|
|
State == CharacterState.Walking) return;
|
|
var lab = GetParentOrNull<Lab>();
|
|
if (lab == null) return;
|
|
var vec = Vector2I.Zero;
|
|
while ((lab.GetMapNodeTypeOfPosition(vec) & Lab.MapNodeType.Walkable) == 0) {
|
|
vec.X = GD.RandRange(0, Lab.MapWidth);
|
|
vec.Y = GD.RandRange(0, Lab.MapHeight);
|
|
}
|
|
|
|
var pos = GlobalPosition;
|
|
if (State == CharacterState.Sitting) {
|
|
StateQueue.Enqueue(CharacterState.StandingUp);
|
|
StateQueue.Enqueue(CharacterState.Walking);
|
|
pos = _lastWalkablePosition;
|
|
}
|
|
else if (State == CharacterState.Idle) {
|
|
State = CharacterState.Walking;
|
|
}
|
|
|
|
StateQueue.Enqueue(CharacterState.Idle);
|
|
MoveFollowPath(lab.GetShortestPath(lab.Point2Coord(pos), vec));
|
|
RandomChangeBody();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 前往座位
|
|
/// </summary>
|
|
public void GoToSeat() {
|
|
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp ||
|
|
State == CharacterState.Walking) return;
|
|
var lab = GetParentOrNull<Lab>();
|
|
if (lab == null) return;
|
|
var i = GD.RandRange(0, 1);
|
|
var target = lab.GetFurSpecialPosition(CubeId, i);
|
|
if (i == 1) {
|
|
// Seat up
|
|
TargetDirection = Direction.Up;
|
|
_targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 12);
|
|
}
|
|
else {
|
|
TargetDirection = Direction.Down;
|
|
_targetSpecialPosition = new Vector2I(target.X * 48 + 24, target.Y * 48 + 18);
|
|
}
|
|
|
|
var pos = GlobalPosition;
|
|
if (State == CharacterState.Sitting) {
|
|
StateQueue.Enqueue(CharacterState.StandingUp);
|
|
StateQueue.Enqueue(CharacterState.Walking);
|
|
pos = _lastWalkablePosition;
|
|
}
|
|
else if (State == CharacterState.Idle) {
|
|
State = CharacterState.Walking;
|
|
}
|
|
|
|
StateQueue.Enqueue(CharacterState.SittingDown);
|
|
StateQueue.Enqueue(CharacterState.Sitting);
|
|
var path = lab.GetShortestPath(lab.Point2Coord(pos), target);
|
|
_lastWalkablePosition = new Vector2I(path.Last().X * 48 + 24, path.Last().Y * 48 + 24);
|
|
// if (lastWalkablePosition.X < targetSpecialPosition.X) {
|
|
// TargetDirection = Direction.Right;
|
|
// } else if (lastWalkablePosition.X > targetSpecialPosition.X) {
|
|
// TargetDirection = Direction.Left;
|
|
// } else {
|
|
// if (lastWalkablePosition.Y < targetSpecialPosition.Y) {
|
|
// TargetDirection = Direction.Up;
|
|
// } else if (lastWalkablePosition.Y > targetSpecialPosition.Y) {
|
|
// TargetDirection = Direction.Down;
|
|
// }
|
|
// }
|
|
MoveFollowPath(path);
|
|
RandomChangeBody();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 随机更换外观
|
|
/// </summary>
|
|
private void RandomChangeBody() {
|
|
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/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/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));
|
|
}
|
|
} |