godot制作flappybird游戏

944 阅读5分钟

首先假设所有人对于godot引擎有一定的了解和知道基本的操作,如果不了解的话可以参考官网的教程大致了解。对于flappybird这个小游戏 我之前是在大学的时候用cocos2dx做过一个demo,后来毕业后就了解了godot引擎就想用这个引擎来进行制作,2017的时候我参考过别人的教程 做了一个,当然比较粗糙(网址下面有),最近就用3.0以上的版本重新做了一遍熟悉了引擎的内容。     对于开始制作游戏的时候,首先要先确定屏幕的大小,这个是根据素材的大小来确定,比如是多少分辨率的,flappybird这个游戏比较简单竖屏 显示,根据别人的素材确定144x256的大小。如果有更高清的图片当然最好了。在确定屏幕的大小后就可以 进行设置工程结构,

图片1.png

这样划分的方式是根据这个引擎的功能来的,godot的开发方式是根据场景树的方式来开发,每个节点又可以添加脚本,场景又可以拼接在一起, 用不同的文件夹可以更好的区分开来。 先构建一个欢迎的界面,这个开始界面的一些节点是其他场景里面拼接过来,在这个界面里面像标题和按钮可以使用ui节点来生成。

图片2.png

图片3.png

根节点使用node2d,对于背景和标题直接使用sprite就可以了,对于按钮可以使用HBoxContainer然后使用TextureButton来做按钮,具体使用可以 参考我的工程里面和官网的说明或者doc。对于按钮的点击事件的话都是跟信号绑定的,可以直接在编辑器设置也可以在脚本中手动连接。 对于地面来说,它是可以移动的,为了看起来连续移动只要在屏幕外添加一块,然后一起移动,另一块移动到屏幕外修改x坐标到右边 屏幕那边就可以了,具体实现如下:

图片4.png

图片5.png

地面的根节点使用StaticBody2D主要是增加碰撞的反馈,它需要添加一个形状,这个形状就设置成图片一样的大小,添加脚本增加移动

图片6.png

在这个脚本里面通过状态的方式来控制地面,状态根据我们自己确定,状态不同使其移动或者停止,移动的时候只要改变x坐标就好了 add_to_group()主要是对节点做一个分类便于查找。 游戏里面有移动的水管,这个跟地面的制作基本是一样的,每次到了屏幕左边就回到屏幕右边,然后随机设置y的坐标,屏幕看起来是 连续的需要3根水管按一定的距离排序。水管的制作如下:

图片7.png

图片8.png

同样的话添加上下两根水管中间留空,中间新建一个很小的矩形主要用来判断小鸟是否进入水管里面用来进行得分,area2d增加一个形状, 绑定body_entered进入的信号,判断是否是小鸟进入了,是的话就发射一个得分的信号。由于我们每个场景节点是独立制作,那么如果拼接 在一起各个节点又是互相依赖的话就不好操作,所以godot有信号的功能,其他节点可以绑定另一个节点的信号来对其他节点发出的信号进行 处理,从而实现互相依赖的关系,如果各个节点又自己互相关联那么代码将变得很零乱,因为这样的话代码的执行就没有一目了然,所以我这边 就没有节点互相绑定,只有在游戏场景中统一对所有的节点统一处理。 对于小鸟的制作,主要是使用物理节点RigidBody2D,它并不向前移动只是上下移动,它的飞行就是增加一个向上的力就可以了,

图片9.png

图片10.png

动画使用AnimatedSprite,这个类是用来添加帧序列的动画,每个动画可以设置fps和是否循环播放,如果不设置循环播放那么在

图片11.png

播放这个动画时候需要$ani.play("flap",true),主要功能是是frame变回原来索引0,因为拍动翅膀这个动画是按下的时候才播放动画 而不是一直播放。具体的动画可以查看我的配置或者doc了解更多使用的方式。 小鸟的主要状态有4种,欢迎界面中的上下飞行,死亡状态,游戏中的状态,默认状态。上下飞行的状态就是上下移动位置,由于RigidBody2D 不好直接更改坐标,所以精灵是可以直接更改坐标的方式来实现。 extends RigidBody2D

var state = game.idle #默认状态

var ypos=3	#上下飞行的距离
var filp=true
var flyspeed=25 #上下飞行的速度

var speed=150	#向上飞的速度

signal birdStateChange  #状态改变


func _ready():#	setState(game.fly)#	print($ani.position.y)
	#setState(game.play)
	add_to_group(game.group_bird)



func _physics_process(delta):
	if state==game.idle:
		idle(delta)
	elif state==game.fly:
		fly(delta)
	elif state==game.play:
		play(delta)
	elif state==game.dead:
		dead(delta)
#设置状态
func setState(newState:int):
	if newState==game.idle:
		gravity_scale=0
	elif newState==game.fly:
		gravity_scale=0
		$ani.play("fly")
	elif newState==game.play:
		gravity_scale=5
		flap()
	elif newState==game.dead:
		angular_velocity=1.4
		$ani.play("idle")
	state=newState
#拍动翅膀
func flap():
	if AudioPlayer:
		AudioPlayer.playSfxWing()
	$ani.play("flap",true)
	linear_velocity.y=-speed
	angular_velocity=-3
		#默认状态
func idle(delta):
	pass
	#上下飞行的状态
func fly(delta):
	if filp:
		if $ani.position.y>ypos:
			filp=false
		else:
			$ani.position.y+=flyspeed*delta
	else:
		if $ani.position.y<-ypos:
			filp=true
		else:
			$ani.position.y-=flyspeed*delta

#游戏开始时 飞行的状态
func play(delta):
	if Input.is_action_just_pressed("ui_accept"):
		flap()
	
	#计算角度避免一直旋转
	if rotation_degrees<-30:
		rotation_degrees=-30
		angular_velocity=0
	
	if linear_velocity.y>0:
		angular_velocity=1.5
	
func dead(delta):
	#print("dead")
	
	pass
#如果碰到水管和地面就发出信号
func _on_bird_body_entered(body):
	print("_on_bird_body_entered")
	if state!=game.play:	#不是开始的状态就跳过
		return
	if body.is_in_group(game.group_ground):
		if AudioPlayer:
			AudioPlayer.playSfxHit()
		emit_signal("birdStateChange")
	elif body.is_in_group(game.group_pipe):
		if AudioPlayer:
			AudioPlayer.playSfxHit()
			AudioPlayer.playSfxDie()
		emit_signal("birdStateChange")
		var other_body = get_colliding_bodies()[0]
		add_collision_exception_with(other_body)

主要的脚本代码都在上面,主要注意的在游戏中的状态,就是要控制角度,角度不能过大,保持在一个范围之内,不然会一直旋转

#计算角度避免一直旋转
if rotation_degrees<-30:
    rotation_degrees=-30
    angular_velocity=0

RigidBody2D需要设置碰撞报告点不然没有碰撞的结果通知,设置成一个点就好了,没有设置就没有碰撞信息。

图片12.png

在这些主要节点制作完毕后基本上就是主要的场景制作,主要场景的功能就是把所有的节点和功能全部包含进来,然后控制游戏的开始和结束

图片13.png

图片14.png

主要场景就这些节点,游戏结束显示的信息和暂停时显示的信息,在这里每个节点可以设置index位置是否显示在最前面,可以根据树的从上到下来 决定显示位置,具体内容可以查看main场景脚本。首先是分数,分数的话需要进行不停的变化,那么主要的话就是计算得分,然后根据位数 生成对应的图片然后添加到节点上,

#获取每个数字
func get_digits(number):
	var str_number = str(number)
	var digits     = []
	
	for i in range(str_number.length()):
		digits.append(str_number[i].to_int())
	return digits

游戏结束时显示得分面板,制作的过程主要使用了texturerect,texturebutton和容器,添加一个动画节点主要是更改面板显示的方式,具体可以看

图片15.png

效果。那么还有一个是暂停界面,那么暂停界面就是显示一个暗色背景的界面,纯色背景是用colorrect,那么在这里的话如果两个按钮的显示层级是

图片16.png

一样的,那么就会出现点不了的情况,所以为什么这些暂停界面得放到树的最后几个,不然就会出现被其他节点覆盖住的情况。 游戏的开始就是把小鸟的状态设置成游戏中的状态,地面开始移动,水管开始以不同的高度开始移动,游戏中只要小鸟碰到地面或者水管游戏 就结束弹出结束的画面,玩家只能控制小鸟的飞行。需要把小鸟的碰撞的信号连接起来用来进行结果的判断。

$bird.connect("birdStateChange",self,"_on_bird_state_change")

#游戏结束
func gameOver()->void:
	#游戏结束
	if $ready.visible:
		$ready.hide()
	state=game.endGame
	$pipe.setState(game.stop)
	$pipe2.setState(game.stop)
        	$pipe3.setState(game.stop)
	$ground.setState(game.stop)
	$ground1.setState(game.stop)
	$bird.setState(game.dead)
	showGameOverPanel()
	setFinalSorce()

#碰到地面和水管
func _on_bird_state_change()->void:
	print('_on_bird_state_change')
	gameOver()

如果小鸟状态发生改变就设置游戏结束。所有的水管和地面停止移动。游戏的暂停就是调用get_tree().paused=true,在每个节点可以设置 暂停的状态,可以设置成继承父节点和继续运行,这样的话一些节点就不受暂停的影响比如暂停界面。

图片17.png

游戏里面需要用到单例的地方,比如声音或者一些全局的配置,godot提供了单例的功能,能把脚本和节点一开始的时候就自动加载,

图片18.png 这里几个脚本和节点就是每个场景都会用到的,声音方面就是使用AudioStreamPlayer来进行播放,不过每一个AudioStreamPlayer 只能播放一种声音,godot本身有提供声音的播放功能比如一些特效的播放,具体没怎么研究,就全都使用AudioStreamPlayer来播放 声音。

图片19.png

extends Node2D

#播放声音

func _ready():
	pass 


func playSfxDie()->void:
	$sfxDie.play()

func playSfxHit()->void:
	$sfxHit.play()
		
func playSfxPoint()->void:
	$sfxPoint.play()

func playSfxSwooshing()->void:
	$sfxSwooshing.play()

func playSfxWing()->void:
	$sfxWing.play()

提供一些方法用来播放声音。 对于画面的抖动的话,就是设置camera2d的offset,在一段时间内offset的改变看起来画面就会抖动了,但是背景需要不动的 话,就需要使用CanvasLayer。CanvasLayer可以设置不跟随摄像机的变化,

图片20.png

#游戏结束
func gameOver()->void:
	#游戏结束
	if $game/ready.visible:
		$game/ready.hide()
	$game/score.visible=false
	$game/btnPause.visible=false
	state=game.endGame
	$game/pipe.setState(game.stop)
	$game/pipe2.setState(game.stop)
	$game/pipe3.setState(game.stop)
	$game/ground.setState(game.stop)
	$game/ground1.setState(game.stop)
	$game/bird.setState(game.dead)
	
	while num<offsetNum:		
		$game/camera.offset.x += rand_range(-magnitude,magnitude)
		$game/camera.offset.y+=rand_range(-magnitude,magnitude)
		num+=1
		yield(get_tree(), "idle_frame")
	num=0
	$game/camera.offset.x=72
	$game/camera.offset.y=128

主要的话改变摄像机位置的时候需要停留一段时间,在函数中需要yield(get_tree(), "idle_frame") ,这个主要作用就是暂停1帧的时间,

具体内容可以参考 godotengine.org/qa/19422/wh…

其他内容有在补充。

godot引擎:godotengine.org/

网址:github.com/absolve/god…

在里面flappybird1文件里面就是工程,godot使用3.0以上版本

bitbucket.org/EdwardAngel…

这个是godot2.0 的flappybird教程可以进行参考,我参考了一些