godot横板移动平台游戏学习笔记

owofile Lv5

godot横板移动平台游戏学习笔记

2D战斗

基础移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extends Area2D
@onready var icon = $Icon
#移动速率
@export var speed = 100
func \_ready():
pass
func \_process(delta):
var direction = Vector2.ZERO
if Input.is\_action\_pressed("move\_right"):
direction.x = 1
if Input.is\_action\_pressed("move\_left"):
direction.x = -1
if Input.is\_action\_pressed("move\_up"):
direction.y = -1
if Input.is\_action\_pressed("move\_down"):
direction.y = 1
position += direction \* delta \* speed

通过每帧获取当前节点二位向量位置,根据Input按键输入判断移动当当前节点位置

然后然后* delta是为了稳定帧率下的运行 *speed是为了灵活的调节移动速度

重力碰撞检测和动画播放

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
extends CharacterBody2D
@onready var animation\_player = $AnimationPlayer
@onready var sprite\_2d = $Sprite2D
const GRAVITY = 2000.0
const WALK\_SPEED = 200
#跳跃高度
const JUMP\_FORCE = 750
#跳跃判断
var is\_jumping = false
func \_physics\_process(delta):
print(velocity.y)
#物体和墙壁发生了碰撞
if is\_on\_wall():
print("撞墙")
#没有上下的撞墙判断 只有一个力
if is\_on\_wall\_only():
print("空中撞墙撞墙")
if velocity.y == -JUMP\_FORCE:
print("jump")
is\_jumping = false
velocity.y += delta \* GRAVITY
if Input.is\_action\_pressed("move\_left"):
velocity.x = -WALK\_SPEED
sprite\_2d.flip\_h = true
animation\_player.play("walk")
elif Input.is\_action\_pressed("move\_right"):
velocity.x = WALK\_SPEED
sprite\_2d.flip\_h = false
animation\_player.play("walk")
else:
velocity.x = 0
move\_and\_slide()
func \_process(delta):
var direction = Input.get\_action\_strength("move\_right") - Input.get\_action\_strength("move\_left")
if is\_jumping:
#animation\_player.play("jump")
pass
elif direction == 0:
animation\_player.play("idle")
func \_input(event):
if event.is\_action\_pressed("move\_up") and not is\_jumping and velocity.y == 0:
velocity.y = -JUMP\_FORCE
is\_jumping = true

使用move_and_slide()时,会检测碰撞并且滑动,从而实现了任意位置放置后,根据重力下落,然后进行移动

播放动画则是根据不同时机的判断完成

窗口尺寸问题

如果出现运行场景后,窗口的尺寸会随着放大缩小发生改变,通过修改项目设置—-窗口—–模式—–canvas_items来让他自适应

教学视频:https://www.bilibili.com/video/BV1Se411k7kS/?spm_id_from=333.337.search-card.all.click&vd_source=adbd70f3395ed4825cbab1afe7aa152e

实现变速移动

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
extends CharacterBody2D
@onready var animation\_player = $AnimationPlayer
@onready var sprite\_2d = $Sprite2D
const GRAVITY = 2000.0
const WALK\_SPEED = 200
const JUMP\_FORCE = 750
var is\_jumping = false
var walk\_acceleration = 0.0
var walk\_timer = 0.0
const WALK\_ACCELERATION\_TIME = 2.0
func \_physics\_process(delta):
if is\_on\_wall():
print("撞墙")
if is\_on\_wall\_only():
print("空中撞墙撞墙")
if velocity.y == -JUMP\_FORCE:
print("jump")
is\_jumping = false
velocity.y += delta \* GRAVITY
if Input.is\_action\_pressed("move\_left"):
velocity.x = -WALK\_SPEED
sprite\_2d.flip\_h = true
animation\_player.play("walk")
elif Input.is\_action\_pressed("move\_right"):
if walk\_timer >= WALK\_ACCELERATION\_TIME:
walk\_acceleration = 2.0 # 例如:超过2秒后加倍加速度
else:
walk\_acceleration = 1.0 # 正常加速度
velocity.x = WALK\_SPEED \* walk\_acceleration
sprite\_2d.flip\_h = false
animation\_player.play("walk")
else:
velocity.x = 0
walk\_acceleration = 0.0
move\_and\_slide()
func \_process(delta):
var direction = Input.get\_action\_strength("move\_right") - Input.get\_action\_strength("move\_left")
if is\_jumping:
# animation\_player.play("jump")
pass
elif direction == 0:
animation\_player.play("idle")
if Input.is\_action\_pressed("move\_right"):
walk\_timer += delta
else:
walk\_timer = 0.0
func \_input(event):
if event.is\_action\_pressed("move\_up") and not is\_jumping and velocity.y == 0:
velocity.y = -JUMP\_FORCE
is\_jumping = true
if event.is\_action\_pressed("Sprint"):
print("向右冲刺")
velocity.x = -600

通过每帧判断,然后时间到了归0

LimboAI

使用这个插件来快速构建项目的状态机,状态机也可以依靠纯手写脚本构建,但是为了追求效率,目前先使用轮子,之后为了更加详细的了解状态机,会自己从头在造轮子。

使用LimboAI自带的函数可以快速创建各种状态,通过使用start 和 update 来控制状态之间的切换。

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
extends CharacterBody2D
@onready var animation\_player = $AnimationPlayer
@onready var sprite\_2d = $Sprite2D
@onready var trail\_timer = $TrailTimer
@onready var label = $"../Label"
#状态机
var main\_sm: LimboHSM
const GRAVITY = 2000.0
const WALK\_SPEED = 200
const JUMP\_FORCE = 750
var speed : int
#当前状态
var state : String
var is\_jumping = false
var walk\_acceleration = 0.0
var walk\_timer = 0.0
const WALK\_ACCELERATION\_TIME = 0.5
func \_ready():
initate\_start\_machine()
func \_physics\_process(delta):
label.text = "speed:" + str(speed) + "\n" + "Player状态:" + str(main\_sm.get\_active\_state())
if is\_on\_wall():
print("撞墙")
state = "落地后检测到碰撞"
if is\_on\_wall\_only():
print("空中撞墙撞墙")
state = "浮空下检测到碰撞"
if velocity.y == -JUMP\_FORCE:
print("jump")
is\_jumping = false
velocity.y += delta \* GRAVITY
if Input.is\_action\_pressed("move\_left"):
if walk\_timer >= WALK\_ACCELERATION\_TIME:
walk\_acceleration = 2.0 # 例如:超过2秒后加倍加速度
else:
walk\_acceleration = 1.0 # 正常加速度
speed = -WALK\_SPEED \* walk\_acceleration
velocity.x = speed
#print(-WALK\_SPEED \* walk\_acceleration)
sprite\_2d.flip\_h = true
animation\_player.play("walk")
elif Input.is\_action\_pressed("move\_right"):
if walk\_timer >= WALK\_ACCELERATION\_TIME:
walk\_acceleration = 2.0 # 例如:超过2秒后加倍加速度
else:
walk\_acceleration = 1.0 # 正常加速度
velocity.x = WALK\_SPEED \* walk\_acceleration
#print(WALK\_SPEED \* walk\_acceleration)
sprite\_2d.flip\_h = false
animation\_player.play("walk")
else:
velocity.x = 0
walk\_acceleration = 0.0
move\_and\_slide()
func \_process(delta):
var direction = Input.get\_action\_strength("move\_right") - Input.get\_action\_strength("move\_left")
if is\_jumping:
# animation\_player.play("jump")
pass
elif direction == 0:
animation\_player.play("idle")
#通过帧率对加速度进行计时判断
if Input.is\_action\_pressed("move\_right") or Input.is\_action\_pressed("move\_left"):
walk\_timer += delta
else:
walk\_timer = 0.0
#调用反转函数
flip\_sprite(direction)
func \_input(event):
if event.is\_action\_pressed("move\_up") and not is\_jumping and velocity.y == 0:
velocity.y = -JUMP\_FORCE
is\_jumping = true
if event.is\_action\_pressed("Sprint"):
print("冲刺")
elif event.is\_action\_released("Sprint"):
print("释放冲刺")
func \_on\_trail\_timer\_timeout():
if velocity.x == 0:
return
var trail = preload("res://effects/trail.tscn").instantiate()
get\_parent().add\_child(trail)
get\_parent().move\_child(trail,get\_index())
var properties = [
"hframes",
"vframes",
"frame",
"texture",
"global\_position",
"flip\_h",
]
for name in properties:
trail.set(name,sprite\_2d.get(name))
func flip\_sprite(dir):
if dir == 1:
#人物反转
#print("人物右转")
pass
elif dir == -1:
#print("人物左转")
pass
func \_unhandled\_input(event):
if event.is\_action\_pressed("move\_up"):
main\_sm.dispatch(&"to\_jump")
elif event.is\_action\_pressed("Sprint"):
#冲刺
pass
elif event.is\_action\_pressed("attack"):
main\_sm.dispatch(&"to\_attack")
func initate\_start\_machine():
#实例化状态机
main\_sm = LimboHSM.new()
#添加状态机
add\_child(main\_sm)
#创建idle状态 设置默认状态的启动方法 on\_enter 首次启动调用方法 update 更新后调用方法
var idle\_state = LimboState.new().named("idle").call\_on\_enter(idle\_start).call\_on\_update(idle\_update)
#创建walk状态
var walk\_state = LimboState.new().named("walk").call\_on\_enter(walk\_start).call\_on\_update(walk\_update)
#创建jump状态 设置默认状态的启动方法 on\_enter 首次启动调用方法 update 更新后调用方法
var jump\_state = LimboState.new().named("jump").call\_on\_enter(jump\_start).call\_on\_update(jump\_update)
#创建walk状态
var attack\_state = LimboState.new().named("attack").call\_on\_enter(attack\_start).call\_on\_update(attack\_update)
#加入状态机
main\_sm.add\_child(idle\_state)
main\_sm.add\_child(walk\_state)
main\_sm.add\_child(jump\_state)
main\_sm.add\_child(attack\_state)
#设置初始状态
main\_sm.initial\_state = idle\_state
#状态机切换
#从闲置状态切换到行走状态
main\_sm.add\_transition(idle\_state,walk\_state,&"to\_walk")
#从任意状态都可以回到闲置状态 这也符合游戏人物移动逻辑
main\_sm.add\_transition(main\_sm.ANYSTATE,idle\_state,&"state\_ended")
#两个转换,公用一个调用方法 实现更灵活的调用
main\_sm.add\_transition(idle\_state,jump\_state,&"to\_jump")
main\_sm.add\_transition(walk\_state,jump\_state,&"to\_jump")
#任何状态都可以进行攻击的切换
main\_sm.add\_transition(main\_sm.ANYSTATE,attack\_state,&"to\_attack")
#初始化状态机
main\_sm.initialize(self)
#设置状态机激活状态 活动状态机
main\_sm.set\_active(true)
#idle的首次启动调用方法
func idle\_start():
print("idle start")
#可以播放闲置动画
#idle的每帧更新调用方法
func idle\_update(delta: float):
if velocity.x != 0:
main\_sm.dispatch(&"to\_walk")
#walk的首次启动调用方法
func walk\_start():
print("角色行走中")
#walk的每帧更新调用方法
func walk\_update(delta: float):
if velocity.x == 0:
main\_sm.dispatch(&"state\_ended")
print("角色行走结束")
#jump的首次启动调用方法
func jump\_start():
#调用动画播放Jump动画
#改变velocity.y = jump\_power 来实现跳跃
pass
#jump的每帧更新调用方法
func jump\_update(delta: float):
#判断跳跃是否落地
if is\_on\_floor():
#落地后切换闲置状态
main\_sm.dispatch(&"state\_ended")
#attack的首次启动调用方法
func attack\_start():
#播放攻击动画
pass
#attack的每帧更新调用方法
func attack\_update(delta: float):
#当播放动画结束后,切换状态机回到闲置状态
#由于目前没有这个动画所以只用注释解释 后续可以根据情况修改
pass

视频教学:https://www.bilibili.com/video/BV1Y6421Z7sV/?spm_id_from=333.1007.top_right_bar_window_default_collection.content.click&vd_source=adbd70f3395ed4825cbab1afe7aa152e

里面的一些状态我并没有用上,因为之前我就是直接在脚本里判断写的移动,不好桥接,所以只是做了一些演示。实际代码都用注释去讲了。

但还是一个很好的学习项目

道具范围拾取检测

实现碰撞检测需要的关键节点是Area2D,它可以检测进入到区域的所有同类

这是我的Player场景构成

QQ截图20240630164451

其中有两个CollisionShape2D,但作用完全不一样,Player下的Col节点是为了实现地面的碰撞,可以实现基础的移动,跳跃。

而Area2D,本质上不会达成碰撞,而是一个检测区域的作用。

这是我的道具场景构成:

QQ截图20240630164700

可以看到,我使用Area2D的信号返回给了Boom节点,然后当Area2D的Coll检测到同类Arrea2D碰撞后,也就是进入这个区域内,会启动这个信号。

QQ截图20240630164818

就这样,我们实现了基础的道具拾取检测,也可以用来完成其他的功能

Tween

使用Tween可以通过代码直接创建动画,无需创建节点

学习视频:https://www.bilibili.com/video/BV1Cy411z7xE/?spm_id_from=333.1007.tianma.1-1-1.click&vd_source=adbd70f3395ed4825cbab1afe7aa152e

场景切换

实现场景切换需要三个节点

CanvasLayer 父节点

ColorRect 子节点 用来进行切换的动画效果

AnimationPlayer子节点 用来进行切换的动画效果

首先用ColorRect制作一个黑幕动画,画面从透明变成黑色

父节点脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extends CanvasLayer
@onready var animation\_player = $AnimationPlayer
func \_ready():
#隐藏自身
self.hide()
func changer\_scene(path):
self.show()
self.set\_layer(999)
animation\_player.play("changer")
await animation\_player.animation\_finished
get\_tree().change\_scene\_to\_file(path)
animation\_player.play\_backwards("changer")
await animation\_player.animation\_finished
self.set\_layer(-1)
self.hide()
pass
  • Title: godot横板移动平台游戏学习笔记
  • Author: owofile
  • Created at : 2024-07-01 11:01:26
  • Updated at : 2025-04-12 14:29:34
  • Link: https://owofile.github.io/blog/2024/07/01/godot横板移动平台游戏学习笔记/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments