Files
f9-web-ai-test/TempleRun/GameModel.cs
T
2026-10-08 10:43:59 +08:00

203 lines
6.9 KiB
C#

using System.Drawing.Drawing2D;
namespace TempleRun;
/// <summary>
/// 全局常量与可调参数。想改手感直接改这里。
/// 世界坐标约定:X 向右为正,Y 离地向上为正,Z 玩家前方为正。
/// 玩家永远停在 Z = 0,世界向 Z 负方向滚动。
/// </summary>
public static class Cfg
{
// ---- 相机(透视投影参数)----
public const float Focal = 520f; // 焦距:越大视野越窄、远处物体越大
public const float CamZ = 235f; // 相机位于玩家后方多远
public const float CamHeight = 142f; // 相机离地高度
public const float FarZ = 3000f; // 渲染最远距离
public const float MinDepth = 45f; // 相机空间最小深度,防止除零
// ---- 跑道 ----
public const int LaneCount = 3;
public const float LaneWidth = 112f; // 单条车道宽
public const float RoadHalfWidth = LaneWidth * 1.5f;
// ---- 玩家 ----
public const float PlayerHeight = 62f; // 站立高度
public const float SlideHeight = 30f; // 滑铲高度
public const float PlayerHalfDepth = 22f;
public const float JumpSpeed = 430f;
public const float Gravity = 950f;
public const float LaneLerp = 13f; // 换道插值速度
public const float SlideDuration = 0.55f;
// ---- 速度与难度 ----
public const float StartSpeed = 620f;
public const float MaxSpeed = 1900f;
public const float SpeedAccel = 3.4f; // 每秒自然加速
public const float SpeedPerCoin = 5f; // 每金币额外加速
public const float SpawnIntervalMin = 0.62f; // 相邻障碍组最小时间间隔(秒)
public const float SpawnIntervalMax = 1.15f;
// ---- 颜色(丛林日落 + 古神庙)----
public static readonly Color SkyTop = Color.FromArgb(30, 34, 72);
public static readonly Color SkyMid = Color.FromArgb(122, 78, 96);
public static readonly Color SkyHorizon = Color.FromArgb(240, 168, 96);
public static readonly Color Fog = Color.FromArgb(200, 152, 118);
public static readonly Color GroundNear = Color.FromArgb(44, 36, 28);
public static readonly Color GroundFar = Color.FromArgb(122, 100, 78);
public static readonly Color RoadA = Color.FromArgb(178, 150, 110);
public static readonly Color RoadB = Color.FromArgb(156, 130, 94);
public static readonly Color Stone = Color.FromArgb(120, 112, 104);
public static readonly Color Wood = Color.FromArgb(122, 82, 48);
public static readonly Color Vine = Color.FromArgb(74, 116, 62);
public static readonly Color Gold = Color.FromArgb(255, 202, 62);
public static readonly Color GoldDark = Color.FromArgb(196, 140, 24);
public static readonly Color Player = Color.FromArgb(226, 92, 62);
public static float LaneX(int lane) => (lane - 1) * LaneWidth;
public static Color Lerp(Color a, Color b, float t)
{
t = Math.Clamp(t, 0f, 1f);
return Color.FromArgb(
(int)(a.R + (b.R - a.R) * t),
(int)(a.G + (b.G - a.G) * t),
(int)(a.B + (b.B - a.B) * t));
}
}
public enum RunState { Ready, Playing, Paused, GameOver }
public enum ObstacleKind
{
/// <summary>矮障碍:必须跳跃</summary>
Low,
/// <summary>悬空横梁:必须滑铲</summary>
High,
/// <summary>整道石墙:必须换道</summary>
Full
}
/// <summary>透视相机:把世界坐标投影到屏幕坐标。</summary>
public static class Cam
{
public static float CenterX;
public static float HorizonY;
public static int Width;
public static int Height;
public static void Setup(int w, int h)
{
Width = w;
Height = h;
CenterX = w * 0.5f;
HorizonY = h * 0.30f;
}
/// <summary>
/// 投影一个世界点。
/// scale 为"每世界单位对应多少像素",用于缩放实体自身尺寸。
/// </summary>
public static void Project(float x, float y, float z, out float sx, out float sy, out float scale)
{
float depth = z + Cfg.CamZ;
if (depth < Cfg.MinDepth) depth = Cfg.MinDepth;
scale = Cfg.Focal / depth;
sx = CenterX + x * scale;
sy = HorizonY + (Cfg.CamHeight - y) * scale;
}
/// <summary>只投影地面上的点(y = 0)。</summary>
public static void ProjectGround(float x, float z, out float sx, out float sy, out float scale)
=> Project(x, 0f, z, out sx, out sy, out scale);
/// <summary>距离雾化系数:0 = 完全清晰,1 = 完全融入雾色。</summary>
public static float FogAmount(float z)
{
float t = Math.Clamp(z / Cfg.FarZ, 0f, 1f);
return t * t; // 平方衰减,近处保持通透
}
}
public class Obstacle
{
public float Z;
public int Lane;
public ObstacleKind Kind;
public bool Scored; // 是否已计入"躲避成功"
public float Width => Kind == ObstacleKind.Full ? 96f : 90f;
public float Depth => Kind == ObstacleKind.Full ? 54f : 56f;
public float BottomY => Kind == ObstacleKind.High ? 40f : 0f;
public float TopY => Kind switch
{
ObstacleKind.Low => 46f, // 低于跳跃高度,跳过去
ObstacleKind.High => 98f, // 悬空横梁,滑铲钻过去
_ => 108f // 整道石墙,只能换道
};
public float CenterX => Cfg.LaneX(Lane);
}
public class Coin
{
public float Z;
public int Lane;
public float Y = 36f;
public bool Taken;
public float Phase;
public float CenterX => Cfg.LaneX(Lane);
}
public class Particle
{
public float X, Y, Z;
public float Vx, Vy, Vz;
public float Life;
public float MaxLife;
public float Size;
public float GravityScale = 1f;
public Color Color;
}
/// <summary>路边装饰物:石柱、火把、树、雕像碎块。</summary>
public class SideProp
{
public float Z;
public int Side; // -1 左,+1 右
public int Type; // 0=石柱 1=火把 2=枯树 3=碎石
public float Height;
public float Phase;
public float CenterX => Side * (Cfg.RoadHalfWidth + 205f + (Type == 3 ? 40f : 0f));
}
public class Player
{
public float X; // 当前横向世界坐标(插值中)
public int TargetLane = 1; // 目标车道 0/1/2
public float Y; // 离地高度
public float Vy;
public bool OnGround = true;
public bool Sliding;
public float SlideTimer;
public float RunPhase; // 跑动动画相位
public bool Alive = true;
public float Tilt; // 换道时的侧倾(视觉)
public float Height => Sliding ? Cfg.SlideHeight : Cfg.PlayerHeight;
public void Reset()
{
TargetLane = 1;
X = Cfg.LaneX(1);
Y = 0;
Vy = 0;
OnGround = true;
Sliding = false;
SlideTimer = 0;
RunPhase = 0;
Alive = true;
Tilt = 0;
}
}