godot 学习 CS基础配置代码 凝雨 2025-03-15 2025-03-15 脚本配置 按键 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class GAME_KEY { public const string lift = "ui_left" ; public const string right = "ui_left" ; public const string up = "ui_left" ; public const string down = "ui_left" ; public readonly static string Moue_lift = "ui_lift" ; public readonly static string Moue_right = "ui_right" ; public readonly static string KEY_A="key_a" ; public readonly static string KEY_B="key_b" ; public readonly static string KEY_C="key_c" ; public readonly static string KEY_D="key_d" ; public readonly static string KEY_E="key_e" ; public readonly static string KEY_F="key_f" ; public readonly static string KEY_G="key_g" ; public readonly static string KEY_H="key_h" ; public readonly static string KEY_I="key_i" ; public readonly static string KEY_J="key_j" ; public readonly static string KEY_K="key_k" ; public readonly static string KEY_L="key_l" ; public readonly static string KEY_M="key_m" ; public readonly static string KEY_N="key_n" ; public readonly static string KEY_O="key_o" ; public readonly static string KEY_P="key_p" ; public readonly static string KEY_Q="key_q" ; public readonly static string KEY_R="key_r" ; public readonly static string KEY_S="key_s" ; public readonly static string KEY_T="key_t" ; public readonly static string KEY_U="key_u" ; public readonly static string KEY_V="key_v" ; public readonly static string KEY_W="key_w" ; public readonly static string KEY_X="key_s" ; public readonly static string KEY_Y="key_y" ; public readonly static string KEY_Z="key_z" ; public readonly static string KEY_0="key_0" ; public readonly static string KEY_1="key_1" ; public readonly static string KEY_2="key_2" ; public readonly static string KEY_3="key_3" ; public readonly static string KEY_4="key_4" ; public readonly static string KEY_5="key_5" ; public readonly static string KEY_6="key_6" ; public readonly static string KEY_7="key_7" ; public readonly static string KEY_8="key_8" ; public readonly static string KEY_9="key_9" ; public readonly static string Min_0="min_0" ; public readonly static string Min_1="min_1" ; public readonly static string Min_2="min_2" ; public readonly static string Min_3="min_3" ; public readonly static string Min_4="min_4" ; public readonly static string Min_5="min_5" ; public readonly static string Min_6="min_6" ; public readonly static string Min_7="min_7" ; public readonly static string Min_8="min_8" ; public readonly static string Min_9="min_9" ; public readonly static string F_0="f_0" ; public readonly static string F_1="f_1" ; public readonly static string F_2="f_2" ; public readonly static string F_3="f_3" ; public readonly static string F_4="f_4" ; public readonly static string F_5="f_5" ; public readonly static string F_6="f_6" ; public readonly static string F_7="f_7" ; public readonly static string F_8="f_8" ; public readonly static string F_9="f_9" ; public const string Ender = "ui_accept" ; public readonly static string Space = "ui_select" ; public readonly static string Escape = "ui_cancel" ; public readonly static string Tab = "ui_focus_next" ; public readonly static string End = "ui_end" ; public readonly static string Shift_Tab = "ui_focus_prev" ; }
JSON xlsx和json转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class MyJson { public void CreateJson (string xlsxpath,string jsonpath ) { using (var workbook = new XLWorkbook(xlsxpath)) { var worksheet = workbook.Worksheet(1 ); var dataTable = new System.Data.DataTable(); foreach (var row in worksheet.RowsUsed()) { if (row.RowNumber() == 1 ) { foreach (var cell in row.CellsUsed()) { dataTable.Columns.Add(cell.Value.ToString()); } } else { var dataRow = dataTable.NewRow(); foreach (var cell in row.CellsUsed()) { dataRow[cell.Address.ColumnNumber - 1 ] = cell.Value.ToString(); } dataTable.Rows.Add(dataRow); } } string json = JsonConvert.SerializeObject(dataTable, Formatting.Indented); File.WriteAllText(jsonpath, json); GD.Print(json); } } }
读取JSON
方法1,这个会将读取到的值映射到对象的属性(需要对象和JSON匹配)
1 2 3 string json = File.ReadAllText(Directory.GetCurrentDirectory() + "//Json/Player.json" );主场景 obj = System.Text.Json.JsonSerializer.Deserialize<主场景>(json); GD.Print(obj);
1 2 3 4 5 6 7 8 string jsontext = File.ReadAllText(Directory.GetCurrentDirectory() + "//Json/Player.json" );using (JsonDocument doc=JsonDocument.Parse(jsontext)) { JsonElement root=doc.RootElement; string pro=root.GetProperty("id" ).ToString(); GD.Print(doc); }
1 2 3 4 5 6 using (FileStream fs = File.OpenRead("path_to_your_json_file" )){ YourClass obj = await JsonSerializer.DeserializeAsync<YourClass>(fs); }
序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using System;using System.Text.Json;namespace JsonExample { class Program { static void Main (string [] args ) { public class Person { public string Name { get ; set ; } public int Age { get ; set ; } } Person person = new Person { Name = "John" , Age = 30 }; string jsonString = JsonSerializer.Serialize(person); Console.WriteLine(jsonString); string json = @"{""Name"":""Alice"",""Age"":25}" ; Person deserializedPerson = JsonSerializer.Deserialize<Person>(json); Console.WriteLine($"姓名: {deserializedPerson.Name} , 年龄: {deserializedPerson.Age} " ); } } }
GUI 背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public partial class BoxFlatResource : StyleBoxFlat { public BoxFlatResource () { BgColor=new Color(0.756f ,0.736f ,0.585f ,1 ); DrawCenter = true ; Skew =new Vector2(0 , 0 ); CornerDetail = 8 ; SetBorderWidthAll(2 ); BorderBlend = false ; BorderColor = new Color(0 , 0 , 0 , 1 ); SetCornerRadiusAll(3 ); SetExpandMarginAll(10 ); ShadowColor = new Color(0 , 0 , 0 ,1 ); ShadowSize = 0 ; ShadowOffset=new Vector2(0 ,0 ); AntiAliasing = true ; AntiAliasingSize = 1 ; SetContentMarginAll(8 ); } }
2D绘制
居中需要将节点和图片的中心位置算出来,然后用节点的中心减去图片的中心位置,就可以得到图片相对节点居中的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Texture2D imageTexture = ResourceLoader.Load<Texture2D>("res://icon.svg" ); base ._Draw(); Vector2 textureSize1 = imageTexture.GetSize(); Vector2 textureSize = new Vector2(textureSize1.X/2 , textureSize1.Y/2 ); Vector2 nodeSize = Size; Vector2 centerPosition = new Vector2(nodeSize.X / 2 - textureSize.X / 2 , nodeSize.Y / 2 - textureSize.Y / 2 ); Rect2 rect = new Rect2(centerPosition, textureSize); DrawTextureRect(imageTexture, rect, false ); var font = ThemeDB.FallbackFont; var font_size = 15 ; string text = "99" ; Vector2 position = new Vector2(45 ,60 ); DrawString(font, position, text, HorizontalAlignment.Left, -1 , font_size); base ._Draw();
第三人称C#控制器
条件
需要2个脚本,一个负责旋转相机,一个负责控制角色,两个脚本的节点虽然互相依赖,不过脚本变量不需要
相机节点放在Node3D节点作为自己,通过旋转Node3D节点控制相机朝向,就是说相机始终是朝着Node3D节点看的,当Node3D节点旋转时,相机也相对跟着旋转从而达到一个聚焦在一个点上旋转的目的
玩家节点的话需要创建两个方法,一个用于控制玩家的移动,一个用于控制玩家的旋转,控制玩家移动需要获得Node3D节点来计算,而计算过后还用于控制玩家的旋转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using Godot;public partial class PlayerCamera : Node3D { private bool 鼠标输入; private float 鼠标输入旋转; private float 鼠标输入倾斜; private float 灵敏度 = 0.25f ; private Vector3 欧拉旋转; [Export ] public float TiltUpperLimit = Mathf.DegToRad(-60.0f ); [Export ] public float TiltLowerLimit = Mathf.DegToRad(60.0f ); public override void _Ready() { Input.MouseMode = Input.MouseModeEnum.Captured; } public override void _PhysicsProcess(double delta) { 欧拉旋转.X += 鼠标输入倾斜 * (float )delta; 欧拉旋转.Y += 鼠标输入旋转 * (float )delta; Transform3D newTransform = new Transform3D(Basis.FromEuler(欧拉旋转), Transform.Origin); Transform = newTransform; 鼠标输入倾斜 = 0.0f ; 鼠标输入旋转 = 0.0f ; } public override void _Input(InputEvent @event) { 鼠标输入 = @event is InputEventMouseMotion && Input.MouseMode == Input.MouseModeEnum.Captured; if (鼠标输入 && @event is InputEventMouseMotion aa) { 鼠标输入旋转 = -aa.Relative.X * 灵敏度; 鼠标输入倾斜 = -aa.Relative.Y * 灵敏度; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 using Godot;public partial class 玩家控制器 : CharacterBody3D { private Node3D _相机父节点; public Node3D 活动物体; public Vector3 旋转向量 = Vector3.Forward; [Export ] public float 跳跃力 = 5.0f ; [Export ] public float 移动速度=10 ; [Export ]public float 旋转速度= 10.0f ; public override void _Ready() { _相机父节点 = GetNode<Node3D>("Node3D" ); 活动物体 = GetNode<Node3D>("机器人" ); } public override void _PhysicsProcess(double delta) { Vector3 velocity = Velocity; if (!IsOnFloor()) { velocity += GetGravity() * (float )delta; } if (Input.IsActionJustPressed("ui_accept" ) && IsOnFloor()) { velocity.Y = 4.5f ; } Vector3 移动方向 = 角色移动(); if (移动方向.Length() > 0.2f ) { 旋转向量 = 移动方向.Normalized(); } 角色旋转(旋转向量, (float )delta); float 速度Y = velocity.Y; 移动方向.Y = 0.0f ; velocity = velocity.Lerp(移动方向 * 移动速度, 10.0f * (float )delta); if (移动方向.Length() == 0.0f && velocity.Length() < 1.0f ) { Velocity = Vector3.Zero; } velocity = new Vector3(velocity.X, 速度Y, velocity.Z); Velocity = velocity; MoveAndSlide(); } private void 角色旋转(Vector3 朝向, float delta) { var 左轴 = Vector3.Up.Cross(朝向); var 基础旋转 = new Basis(左轴, Vector3.Up, 朝向).GetRotationQuaternion(); var 角色缩放 = 活动物体.Transform.Basis.Scale; Transform3D 当前变换 = 活动物体.Transform; Basis 新基础 = new Basis(活动物体.Transform.Basis.GetRotationQuaternion().Slerp(基础旋转, delta * 旋转速度)).Scaled(角色缩放); 当前变换.Basis = 新基础; 活动物体.Transform = 当前变换; } private Vector3 角色移动() { Vector2 输入向量 = Input.GetVector("ui_left" , "ui_right" , "ui_up" , "ui_down" ); Vector3 方向 = Vector3.Zero; 方向.X = -输入向量.X * Mathf.Sqrt(1.0f - 输入向量.Y * 输入向量.Y / 2.0f ); 方向.Z = -输入向量.Y * Mathf.Sqrt(1.0f - 输入向量.X * 输入向量.X / 2.0f ); 方向 = _相机父节点.GlobalTransform.Basis * 方向; 方向.Y = 0.0f ; return -方向; } } } } sis * 输入; 输入.Y = 0.0f ; return -输入; } }
tle=”” alt=”2024911-30813.jpg” width=”647”>tle=”” alt=”2024911-30813.jpg” width=”647”>tle=”” alt=”2024911-30813.jpg” width=”647”>
武器篇 武器 武器射击模式类的设想 设想一:通过枚举,即装备组件切换设计状态 设想二:创建一个部件抽象类,他们有一个抽象向方法,且可以重写,武器调用它的抽象方法,然后用于不同模式,且可以通过部件扩展玩法 设想三:通过武器赋予子弹方向初始位置并创建子弹发射,子弹是否在途中实现自己的逻辑 子弹 子弹位置,方向,旋转 1 2 3 4 5 6 7 global_position=武器.子弹位置 #开始位置 linear_velocity=-武器.子弹方向*武器.子弹速度 #发射方向及速度 方向=子弹位置-目标位置再归一化 #重置旋转 var move_direction = 武器.子弹方向 look_at(global_position + move_direction, Vector3.UP) rotate_object_local(Vector3.RIGHT,deg_to_rad(90))
子弹1:经过一段距离自动销毁,是否会爆炸?,是否穿透敌人?
using Godot;
using System;
public partial class 子弹场景 : MeshInstance3D
{
// 定义子弹的速度
private float _速度 = 10.0f;
// 定义最大移动距离
private float _最大移动距离 = 50.0f;
// 记录子弹的初始位置
private Vector3 _初始位置;
public override void _Ready()
{
// 记录子弹的初始位置
_初始位置 = GlobalTransform.Origin;
}
public override void _PhysicsProcess(double delta)
{
Vector3 方向 = GlobalTransform.Basis.Z;
// 移动子弹
Translate(方向 * _速度 * (float)delta);
// 检查子弹与初始位置的距离
if (GlobalTransform.Origin.DistanceTo(_初始位置) > _最大移动距离)
{
// 超过特定距离后销毁子弹
QueueFree();
}
}
}
#或者
global_position=武器.子弹位置 #开始位置
linear_velocity=-武器.子弹方向*武器.子弹速度 #发射方向及速度
#重置旋转
look_at(global_position - linear_velocity) 位置-速度
rotate_object_local(Vector3.RIGHT,deg_to_rad(90))###查看是否相同
1 2 3 4 ```gdscript var current_direction = linear_velocity.normalized() if not global_position.is_equal_approx(global_position + current_direction):
子弹2:碰到墙面会反弹,是否在地面会滚动?,相对反弹还是角度反弹? 陷阱 地雷:放在地上,怪物碰到会爆炸,对敌人照成伤害
制作思路制作一个地雷类,当怪物碰到时获取敌人信息,并进行减血计算,然后播放爆炸销毁
毒气:当敌人碰撞时,持续对敌人造成伤害
制作思路当放置后开始计时且记录碰撞到的敌人,让其在一定时间后自动销毁,若时间到了记录的敌人还存在,则关闭粗发检测和显示,并计算伤害,当记录的敌人状态全部消失时则销毁
落穴:当敌人碰撞后,生成一个坑,并将敌人陷进去使其无法继续移动 道具 物品类:记录物品的价格数量图标描述属性等 可拾取道具:拥有物品类的信息,记录了物品类的ID,当被拾取时向物品类发出信息更新物品类,然后销毁。 可装备道具:数据存储在物品里,当需要装备时从物品获得数据并实例化一个新的物品装备到玩家身上。 1 2 3 4 5 6 7 # 游戏算法逻辑 ## 怪物移动向玩家 ```gdscript var 方向=(玩家全局位置-怪物全局位置).normalized() 怪物.velocity=方向*速度
获取距离玩家最近的敌人,返回最近敌人节点
1 2 3 4 5 6 7 8 9 10 11 12 func 获取所有怪物并返回离玩家最近的怪物(player:CharacterBody3D): var 怪物数组 = get_children() var nearestEnemy: Node3D = null var nearestDistance: float = INF for enemy in 怪物数组: if enemy is CharacterBody3D: # 假设敌人是 CharacterBody3D 类型 var distance = player.global_position.distance_to(enemy.global_position) if distance < nearestDistance: nearestDistance = distance nearestEnemy = enemy return nearestEnemy
1 2 3 4 5 6 7 8 9 10 11 12 13 func 获取所有怪物并返回离玩家最远的怪物(player: CharacterBody3D) -> Node3D: var enemyArray = get_children() var farthestEnemy: Node3D = null var farthestDistance: float = 0.0 for enemy in enemyArray: if enemy is CharacterBody3D: # 假设敌人是 CharacterBody3D 类型 var distance = player.global_position.distance_to(enemy.global_position) if distance > farthestDistance: farthestDistance = distance farthestEnemy = enemy return farthestEnemy
获取血量最少的敌人,并返回 1 2 3 4 5 6 7 8 9 10 11 12 13 func 获取所有怪物并返回血量最少的怪物(player: CharacterBody3D) -> Node3D: var enemyArray = get_children() var lowestHealthEnemy: Node3D = null var lowestHealth: float = INF for enemy in enemyArray: if enemy is CharacterBody3D: # 假设敌人是 CharacterBody3D 类型 var health = enemy.health # 假设敌人有一个 health 属性 if health < lowestHealth: lowestHealth = health lowestHealthEnemy = enemy return lowestHealthEnemy
GUI 游戏设想
我想制作一个通过部件来强化装备赋予装备效果的游戏,比如通过雷达锁定敌人,通过枪管放大子弹,不过我在开发过程中遇到了难题,就是怎么组合这些武器并给玩家装备上呢,我也希望在GUI上能够实时展示玩家组合后的武器样式。
拖拽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var 物品:武器基类=武器基类.new() @export var color = Color(1, 0, 0, 1) func _get_drag_data(position): #使用不在树中的控件 var cpb = ColorPickerButton.new() cpb.color = color cpb.size = Vector2(50, 50) set_drag_preview(cpb) return 物品 func _can_drop_data(position, data): # 检查是否为武器基类 if data is 武器基类: return true else: return false #数据处理 func _drop_data(position, data): # 处理拖放的数据 print("Dropped data: ", data)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func _get_drag_data(position): if 背包.背包[索引ID]: var cpb =TextureRect.new() cpb.texture=背包.背包[索引ID].icon set_drag_preview(cpb) return 背包.背包[索引ID] func _can_drop_data(position, data): # 检查是否为武器基类 if data is 雷达基类: return true else: return false #数据处理 func _drop_data(position, data): if 背包.背包[索引ID]!=null: var ite=背包.背包.find(data) var temp=背包.背包[索引ID] 背包.背包[索引ID]=data 背包.背包[ite]=temp else : var ite=背包.背包.find(data) 背包.背包[索引ID]=data 背包.背包[ite]=null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 extends PanelContainer @onready var texture_rect: TextureRect = $TextureRect var 背包:装备槽UI var 装备:雷达基类 # Called when the node enters the scene tree for the first time. func _ready() -> void: 背包=get_tree().get_first_node_in_group("装备槽") pass # Replace with function body. # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: 刷新纹理() pass func _gui_input(event: InputEvent) -> void: if Input.is_action_just_pressed("鼠标左"): for i in 背包.背包.size(): if 背包.背包[i]==null: 背包.背包[i]=装备 装备=null pass func 刷新纹理(): if 装备!=null: texture_rect.texture=装备.icon elif 装备==null: texture_rect.texture=preload("res://材质/空纹理.png") #func _get_drag_data(position): #if 装备: #var cpb =TextureRect.new() #cpb.texture=装备.icon #set_drag_preview(cpb) #return 装备 func _can_drop_data(position, data): # 检查是否为武器基类 if data is 雷达基类: return true else: return false #数据处理 func _drop_data(position, data): if 装备!=null: var ite=背包.背包.find(data) var temp=装备 装备=data 背包.背包[ite]=temp 背包.更换雷达.emit(装备) else : var ite=背包.背包.find(data) 装备=data 背包.背包[ite]=null 背包.更换雷达.emit(装备)