godot製做的battle city

    最開始的時候我就想製做一個90坦克的demo,以前看了其餘的遊戲引擎感受很差搞,後來用了godot感受能夠研究一下,最近學着作了一些。雖然看起來可能跟原版有差距,可是大部分功能都有了,增長了一個地圖編輯器。javascript

    截圖:java

 

大體功能就如同上面截圖同樣,截下來就介紹一下實現這個遊戲中基本的難點和godot引擎使用的注意地方。在玩過原版坦克大戰的時候,若是你仔細觀察過就會發現敵人出生的地方若是多輛坦克一塊兒出生的話,剛開始是沒有碰撞檢測,一旦分開了就會有碰撞檢測。磚塊被擊中會有不一樣的形狀,可是它原來的體積仍是在,而且沒法經過。坦克在冰塊上會有滑動。坦克吃了基地變成石頭的道具後,在最後變化的時候,不停的開槍,能夠嵌入裏面,可是能夠移動出來。若是不少敵人坦克擠在一塊兒,不會出現互相卡死的狀況。敵人的ai優先向基地出發。python

那麼若是使用godot的碰撞功能,那麼以上的一些功能可能沒法實現,好比多個坦克重疊的狀況,強行進入磚塊中問題,因此碰撞的檢測須要本身來實現,實現的功能能夠參考一下別人寫的文章:https://developer.ibm.com/technologies/javascript/tutorials/wa-build2dphysicsengine/ 這個具體介紹瞭如何實現一個物理引擎。在碰撞以後須要對碰撞體進行位置的從新設置,這個過程就比較重要了。android

接下來就介紹一下項目的基本結構,主要的話把各個功能分開來比較好處理,level裏面就是每一關的地圖文件。其餘的到時能夠本身打開看看。git

最開始就是實現基本的界面,界面的實現主要使用godot自帶的ui組件,這個ui組件沒有android的ui組件好用,一些佈局實在是坑爹,不是很還用,不少的地方須要本身處理。主要使用水平佈局和垂直佈局,關於字體的的話,這個新建一個動態字體的資源把ttf文件導入而後就可使用,可是這個字體的大小是統一的,若是你想要不一樣大小的字體,只能在新建一個。github

對於玩家和敵人的製做的話,我以前是把它們分開來,估計之後要統一成同一個類,而後繼承後進行修改。碰撞的代碼所有都在腳本里面進行判斷,自己就是進行一個基本的動畫和演示。json

var rect=Rect2(Vector2(-14,-14),Vector2(28,28))
var debug=true
var vec=Vector2.ZERO
var keymap={"up":0,"down":0,"left":0,"right":0,'fire':0}
var level=0 #坦克的級別	0最小 1中等 2是大  3是最大
var dir=0 # 0上 1下 2左 3右
var shootTime=0	
var shootDelay=60
var bullets=[]
var bulletMax=1	#發射最大子彈數
var bullet=Game.bullet
var isInit=false
var state=Game.tank_state.IDLE
var initStartTime=0
var initTime=1200  #ms
var isInvincible=false
var invincibleStartTime=0
var invincibleTime=8000
var isStop=false#是否中止
var playId=2  #1=1p 2=2p
var life=1  #生命默認1
var speed = 70 #移動速度
var bulletPower=Game.bulletPower.normal
var hasShip=false	#是否有船

坦克的基本屬性暫時只有這些,坦克的發射子彈是有時間和個數的限制,實現的方式也不是很複雜,主要經過時間判斷和容器中子彈物體是否無效,固然也能夠在坦克內部添加一個節點用來存儲子彈節點,這樣坦克被摧毀,子彈也會消失。app

#開火
func fire():
	if OS.get_system_time_msecs()-shootTime<shootDelay:
		return
	else:
		shootTime=OS.get_system_time_msecs()
#	print("dir",dir)	
	var del=[]
	for i in bullets: #清理無效對象
	#	print(is_instance_valid(i))
		if not is_instance_valid(i):
			del.append(i)
	for i in del:
		bullets.remove(bullets.find(i))
	if bullets.size()<bulletMax:
		playShot()
		var temp=bullet.instance()
		temp.setType("player")
		temp.position=position
		temp.setPower(bulletPower)
		temp.setDir(dir)
		temp.setPlayerId(playId)
		bullets.append(temp)
		Game.mainScene.add_child(temp)

坦克的移動主要根據按鍵,可是坦克有1p,2p,全部按鍵要進行分類,至於如何動態的修改能夠參考如下項目:https://github.com/nezvers/Godot-GameTemplate ,按鍵的後只要改變座標的話就能夠移動坦克了。編輯器

func _update(delta):
	if state==Game.tank_state.IDLE:
		initStartTime+=delta*1000
		if initStartTime>=initTime:
			initStartTime=0
			isInit=true
			$ani.playing=false
			setState(Game.tank_state.START)
		pass
	elif state==Game.tank_state.START:
		if Input.is_key_pressed(keymap["up"]):
	#		print("up")
			vec.y=-speed
			vec.x=0
			dir=0
			isStop=false
		elif Input.is_key_pressed(keymap["down"]):
			vec.x=0
			vec.y=speed
			dir=1
			isStop=false
		elif Input.is_key_pressed(keymap["left"]):
			vec.x=-speed
			vec.y=0
			isStop=false
			dir=2	
		elif Input.is_key_pressed(keymap["right"]):	
			vec.y=0
			vec.x=speed
			dir=3
			isStop=false
		else:
			vec=Vector2.ZERO	
		
		if vec!=Vector2.ZERO:
			if !$walk.playing:
				$walk.play()
			if $idle.playing:
				$idle.stop()
			pass	
		else:
			if $walk.playing:
				$walk.stop()
			if !$idle.playing:
				$idle.play()
			
		if Input.is_key_pressed(keymap["fire"]):
		#	print("fire")
			fire()	
			
		animation(dir,vec)	
		if !isStop:
			position+=vec*delta
		
		if isInvincible:
			if OS.get_system_time_msecs()-invincibleStartTime>=invincibleTime:
				invincibleStartTime=0
				isInvincible=false
				_invincible.visible=false
				_invincible.playing=false
			
	pass

對於坦克吃到道具變化的話基本都是經過改變紋理的樣子來實現。子彈的設計主要是一張圖片而後移動,碰到牆壁爆炸而後消失。主要有如下幾種屬性。函數

export var dir=2 # 0上 1下 2左 3右
var speed=160  
var type=Game.bulletType.players
var playerID  #玩家id
var power=Game.bulletPower.normal  #1是基本火力 2是最強火力
#var winSize=Vector2(480,416)	#屏幕大小
var size=Vector2(6,8)	#圖片大小
var vec= Vector2.ZERO
var isValid=false
var rect=Rect2(Vector2(-3,-4),Vector2(6,8))

對於遊戲中的每一個物體的碰撞都是在每一個對象裏面添加一個var rect=Rect2(Vector2(-3,-4),Vector2(6,8))。這個rect就是用來進行判斷是否重疊,若是重疊就是發生了碰撞,那這個重疊有幾種狀況就是有的是邊的重疊,有的是兩個矩形重疊面積大。具體能夠參考上面的物理引擎的實現。主要是碰撞後要對位置作調整。

for i in _tank.get_children():	#檢查坦克與磚塊的碰撞
			var rect=i.getRect()
			for y in _brick.get_children():
				if y.get_class()=="brick":
					var type=y.getType() #裝快的類型
					if type==Game.brickType.bush or type==Game.brickType.ice:	#草叢
						continue
					
					var rect1=y.getRect()	
					if rect.intersects(rect1,false):  #碰撞  判斷是否被包圍住
						if rect1.encloses(rect):#徹底疊一塊兒
							continue
							
						var dx=(y.getPos().x-i.position.x)/(y.getXSize()/2)
						var dy=(y.getPos().y-i.position.y)/(y.getYSize()/2)
						var absDX = abs(dx)
						var absDY = abs(dy)

						if abs(absDX - absDY) < .1:
							if dx<0:
								i.position.x=y.getPos().x+y.getXSize()/2+i.getSize()/2			
							else:
								i.position.x=y.getPos().x-y.getXSize()/2-i.getSize()/2	

							if dy<0:
								i.position.y=y.getPos().y+y.getYSize()/2+i.getSize()/2			
							else:
								i.position.y=y.getPos().y-y.getYSize()/2-i.getSize()/2						
						elif absDX > absDY:
							if dx<0:
								i.position.x=y.getPos().x+y.getXSize()/2+i.getSize()/2					
							else:
								i.position.x=y.getPos().x-y.getXSize()/2-i.getSize()/2		
						else:
							if dy<0:
								i.position.y=y.getPos().y+y.getYSize()/2+i.getSize()/2	
							else:
								i.position.y=y.getPos().y-y.getYSize()/2-i.getSize()/2

對於其餘物體的碰撞其實都是這樣,對於動態的物體的碰撞調整可能須要進行一些處理。

對於坦克間的碰撞,這個須要特殊的處理,若是你有玩過以前的版本,你就會發現多輛坦克有重疊在一塊兒的狀況,這種狀況須要進行特殊的處理,我這邊只判斷坦克的前進方向是否有物體,若是有就沒法前進,沒有就能夠前進。可是對於位置不能進行修改,否則下一輛坦克的判斷就會出現能夠前進的問題。這個問題如今看仍是有些地方處理的很差,只能後續處理。

var tanks=_tank.get_children()
		for i in tanks:	#坦克與坦克的碰撞
			var isStop=false
			for y in tanks:
				if i!=y:
					if i.isInit && y.isInit:
						var rect=i.getRect()
						var rect1=y.getRect()
						var iTankDir=i.dir
						var yTankDir=y.dir
						var xVal =i.position.x-y.position.x
						var yVal =i.position.y-y.position.y
						var absXVal=abs(xVal)
						var absYVal=abs(yVal)
						
						if rect.intersects(rect1,false):
							if iTankDir in [0,1]:	#上下
								if absYVal<i.getSize() and absYVal>i.getSize()/2:
									if yVal<0 and iTankDir==1:
										isStop=true
									elif yVal>0	and iTankDir==0:
										isStop=true
										
#									if yVal<0:
#										i.position.y=y.getPos().y-y.getSize()/2-i.getSize()/2			
#									else:
#										i.position.y=y.getPos().y+y.getSize()/2+i.getSize()/2	
								else:
									isStop=false
								pass
							elif iTankDir in [2,3]:	#左右
								if absXVal<i.getSize() and absXVal>i.getSize()/2:			
									if xVal<0 and iTankDir==3:
										isStop=true
									elif xVal>0 and iTankDir==2:
										isStop=true
#									if xVal<0:
#										i.position.x=y.getPos().x-y.getSize()/2-i.getSize()/2	
#									else:
#										i.position.x=y.getPos().x+y.getSize()/2+i.getSize()/2
								else:
									isStop=false
								pass				
					pass
				pass
			i.setStop(isStop)

遊戲裏面有聲音的播放,這個因爲有限制,mp3的沒法播放因此一些用的是ogg,可是ogg一旦播放就會沒法停下來,因此要特殊處理。

var point = $point.stream as AudioStreamOGGVorbis
point.set_loop(false)
var power1= $power1.stream as AudioStreamOGGVorbis
power1.set_loop(false)

在遊戲開始界面的時候,有一個動畫慢慢升起來的標題,這個製做須要準備兩個動畫,而後按下的時候,直接播放結束的那個,具體能夠看下代碼。

func _input(event):
	if event is InputEventKey:
		if event.is_pressed():
			if (event as InputEventKey).scancode==KEY_DOWN:		
				if index<2:
					index+=1
					setMode(index)
			elif (event as InputEventKey).scancode==KEY_UP:
				if index>0:
					index-=1
					setMode(index)
			elif (event as InputEventKey).scancode==KEY_ENTER:	
				if _ani.get_current_animation()=="start" and \
					_ani.is_playing():
					_ani.play("end")
					return
				if mode in [1,2]:
					Game.mode=mode
					Game.changeSceneAni(Game._mainScene)
				else:
					var scene = preload("res://scenes/map.tscn"	)
					var temp=scene.instance()
					temp.mode=1
					queue_free()
					set_process_input(false)
					get_tree().get_root().add_child(temp)
					set_process_input(true)
					#Game.changeSceneAni(Game._welcomeScene)

遊戲中地圖的生成,遊戲裏面自帶了地圖的編輯器,對於遊戲中的地圖的製做,首先地圖是一個26x26的小方塊組成的。幾個特殊的地方沒法編輯的,基地的位置,玩家,敵人出生地都是沒法編輯的,編輯以後數據的並保存主要是以json的格式保存,格式爲{"name":'',"data":[],"base":[],"author":"absolve", "description":""},每一個方塊爲{'x':indexX,'y':indexY,"type":0},方塊的類型是0,1,2,3,4,方塊,石頭,水,草叢,冰塊。讀取的時候根據位置顯示在界面上,這樣基本就成了。界面上的點擊事件主要是靠_input函數,獲取鼠標的事件來判斷是否按下

func _input(event):
	if _fileDiaglog.visible or _loadDiaglog.visible or lock or mode!=1:
		return
	if event is InputEventMouseButton:
		if event.button_index == BUTTON_LEFT and  event.pressed:
			isPress=true
			if currentItem!=-1:	
				if !mapRect.has_point(get_global_mouse_position()):
					return
				if! checkItem(get_global_mouse_position()):
					addItem(get_global_mouse_position())	
			elif currentItem==-1:
				clearItem(get_global_mouse_position())		
		elif !event.pressed:
			isPress=false
	elif event is InputEventMouseMotion:	#移動
		if isPress:
			if currentItem!=-1:	
				if !mapRect.has_point(get_global_mouse_position()):
					return
				if! checkItem(get_global_mouse_position()):
					addItem(get_global_mouse_position())	
			elif currentItem==-1:
				clearItem(get_global_mouse_position())
		pass

計分的畫面的製做,首先須要製做出基本的界面,每一個坦克類型須要判斷是否大於0,而後統計完後進入下一個,直到完成爲止,這個過程只須要更改每一個狀態,直到最後的計數完成爲止,而後進入下關或者遊戲結束。具體能夠看下代碼。

在godor裏面的時間,若是你是在_process(delta)裏面每一幀不是固定的,有時快有時慢,用自帶的定時器就能夠。

 

參考資料:https://github.com/shinima/battle-city

https://github.com/newagebegins/BattleCity

https://github.com/krystiankaluzny/Tanks

https://www.sounds-resource.com/nes/battlecity/sound/3710/

項目地址:https://github.com/absolve/godotgame  (tank文件夾)

其它想到在補充,有啥問題,記得反饋。

相關文章
相關標籤/搜索