Create a Complete 2D Survivors Style Game in Godot 4

1. Starting Out

1. Godot Download, Orientation, and Setup

godot4
关掉3d编辑器create profile
点击3个点可以切换选项卡位置
编辑器->编辑器设置->

2. Creating the Player

kenney拖动图片到godot的res://目录下
新建资源文件夹
click 'other node'
创建characterBody2D, 让角色可以移动
使用精灵节点来让角色节点显示图片ctrl+s保存
重命名重命名拖动png到精灵节点的texture属性上
让图片清晰(当前项目纹理效果和图片的不匹配) ,项目->项目设置->渲染->纹理
让人物的脚站在地面上添加子节点
点击circleshape, 弹出子菜单, 调整大小
调整node2d>transform>position>y为-5
如果使用矩形碰撞结构, 容易卡在墙角(转弯)

3. Player Movement

添加脚本到节点项目->项目设置->输入映射->添加
点击动作右侧+号弹出, 绑定按键监听继续添加
f1可以打开帮助文档

# player.gd
extends CharacterBody2D

const MAX_SPEED = 200

# Called when the node enters the scene tree for the first time.
# 该节点位于场景树中, 一切都可以使用, 此时其子节点都加载完毕
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
# 每帧都会被调用的函数
func _process(delta):
    var movement_vector = get_movement_vector()
    # 归一化 -> 变成长度为1的向量
    var direction = movement_vector.normalized()
    # velocity: 角色的速度属性
    velocity = direction * MAX_SPEED
    # 根据velocity移动
    move_and_slide()

func get_movement_vector():
    var movement_vector = Vector2.ZERO
    
    # 按下-> get_action_strength("move_right")==1, 没按为0, 左右都按抵消==0
    var x_movement = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
    var y_movement = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
    return Vector2(x_movement, y_movement)

点击回到2D添加角色用来移动的场景
重命名为Mainctrl+s保存
按w, 小人身上出现箭头, 拖动小人到右下角, 点击f5或运行按钮-> 提示没有场景-> 点击选择当前-> 运行

4. Creating a TileMap

项目设置 -> 设置游戏窗口大小mode=='viewpoint' -> 低分辨率显示, 更像素化
高级设置->默认窗口大小(我的电脑屏幕比较小, 用1280x720)


拖动tilemap_packed.png到godot项目
拖动tilemap_packed.png到tileset编辑窗口 -> click no单击选择
terrain sets -> add element
点击图块里的小格
切换到概率, 输入概率值后点击图块, 设置该图块被平铺的概率

同时选中tileset, tilemap, 地形栏才有东西左上角切为箭头, 开始绘制地板
点击小方框图标, 可以大范围绘制

5. Game Camera

ctrl+nctrl+actrl+s
ctrl+shift+o
group的作用是可以从任何地方访问的标识符

# game_camera.gd
extends Camera2D


# Called when the node enters the scene tree for the first time.
func _ready():
    make_current()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    # 获取当前场景树, 获取树中节点
    var player_nodes = get_tree().get_nodes_in_group("player")
    if player_nodes.size() > 0:
        var player = player_nodes[0] as Node2D
        global_position = player.global_position

camera添加到场景
让镜头比人物慢一点, 显得更自然, 我们可以使用独立的帧率来实现

# game_camera.gd
extends Camera2D

var target_position = Vector2.ZERO

# Called when the node enters the scene tree for the first time.
func _ready():
    make_current()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    acquire_target()
    # lerp 线性插值 有开始和结束点, 用百分比来表示两个位置的距离的百分比
    # 每一帧都会移动到离target_position 1.0 - exp(-delta * 10)的位置
    global_position = global_position.lerp(target_position, 1.0 - exp(-delta * 10))
    
func acquire_target():
    # 获取当前场景树, 获取树中节点
    var player_nodes = get_tree().get_nodes_in_group("player")
    if player_nodes.size() > 0:
        var player = player_nodes[0] as Node2D
        target_position = player.global_position
        global_position = player.global_position

2. Building the Foundation

1. Creating a Rat Enemy

ctrl+N 创建新场景ctrl+s保存找到敌人图片
拖入项目目录并改名
选中basicEnemy节点,ctrl+a添加精灵拖动敌人图片到精灵节点的texture属性
修改y的值,让老鼠的脚和地面对齐选中basicEnemy节点,ctrl+a添加形状
radius:5position: y: -5
添加脚本

extends CharacterBody2D

const MAX_SPEED = 75

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    var direction = get_direction_to_player()
    velocity = direction * MAX_SPEED 
    move_and_slide()

func get_direction_to_player():
    var player_node = get_tree().get_first_node_in_group("player") as Node2D
    if player_node != null:
        return (player_node.global_position - global_position).normalized()
    return Vector2.ZERO

main场景,按ctrl+shift+a,将敌人添加到场景

现在碰撞后不会粘在一起,可以移动过去

2. Creating the First Sword Ability

新建2d场景
ctrl+按住中间的点, 可以旋转
新建场景
创建脚本export属性, 保存后出现一个新的inspector属性
+子节点timerctrl+shift+a, 添加实例化子节点
关掉, 否则定时器只触发一次

extends Node

# 导出全局变量
@export var sword_ability: PackedScene

# Called when the node enters the scene tree for the first time.
func _ready():
    #get_node('Timer')
    $Timer.timeout.connect(on_timer_timeout)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass 

# 每次触发计时器执行的操作
func on_timer_timeout():
    var player = get_tree().get_first_node_in_group("player") as Node2D
    if player == null:
        return
        
    # 插入到scene: main
    var sword_instance = sword_ability.instantiate() as Node2D
    player.get_parent().add_child(sword_instance)
    sword_instance.global_position = player.global_position

3. Introduction to AnimationPlayer

添加animationPlayer, 实现剑的动画效果

点击小点会出来这个界面
单击第一个

动画改为0.5s, shift拖动出蓝框, 范围选中关键帧, 然后点按拖动到后面



我们需要让动画结束后, 从场景树中移除
右键->插入关键帧在下一个空闲帧时移除
运行游戏, 然后暂停, 点击remote查看当前场景树, 动画被自动移除

4. Targeting Enemies With Sword Ability

给enemy添加分组, 这样就可以在其他节点访问该节点, 获取位置

extends Node

const MAX_RANGE = 150 

# 导出全局变量
@export var sword_ability: PackedScene

# Called when the node enters the scene tree for the first time.
func _ready():
    #get_node('Timer')
    $Timer.timeout.connect(on_timer_timeout)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

# 每次触发计时器执行的操作
func on_timer_timeout():
    var player = get_tree().get_first_node_in_group("player") as Node2D
    if player == null:
        return
        
    var enemies = get_tree().get_nodes_in_group("enemy")
        
    # 过滤掉距离玩家超过150方圆的敌人
    enemies = enemies.filter(func(enemy: Node2D): 
        return enemy.global_position.distance_squared_to(player.global_position) < pow(MAX_RANGE, 2)
    )
    if enemies.size() == 0:
        return
    
    enemies.sort_custom(func(a: Node2D, b: Node2D):
        var a_distance = a.global_position.distance_squared_to(player.global_position)
        var b_distance = b.global_position.distance_squared_to(player.global_position)
        return a_distance < b_distance
    )
        
    # 插入到scene: main
    var sword_instance = sword_ability.instantiate() as Node2D
    player.get_parent().add_child(sword_instance)
    sword_instance.global_position = enemies[0].global_position

5. Destroying Enemies


  目录