Godot3遊戲引擎入門之十三:實現玩家的跳躍功能並完成一個平臺小遊戲(下)

Godot3遊戲引擎入門之十三:實現玩家的跳躍功能並完成一個平臺小遊戲(下)

一、前言

本文爲上一篇文章的續篇,在上一節中我們一起討論了 Godot 中平臺遊戲玩家跳躍功能的實現方法,並使用到了本次小遊戲中,那麼本節內容主要是分享這個平臺小遊戲的基本結構和製作過程。遊戲運行效果如下:

效果圖

遊戲中的圖片資源主要來源於 OpenGameArt.org 網站: https://opengameart.org/content/a-platformer-in-the-forest ,遊戲中的部分音樂資源也是在該網站中找到的,其他部分圖片和音樂是我七零八湊拼起來的,爲了順利完成一個小遊戲,找資源也花費了我不少時間,在這裏建議大家有時間可以學習一些基礎的美術知識吧。

本次兩篇文章所詳細講述的內容包括以下部分:

  1. 重力加速度知識和簡單的跳躍實現方法(上)
  2. 二次跳躍的實現(上)
  3. 精確高度的跳躍實現(上)
  4. 遊戲場景結構與主要代碼
  5. 三種敵人的行爲和實現
  6. 其他的一些效果介紹
  7. 問題和總結

主要內容:平臺遊戲的製作解析
閱讀時間: 10 分鐘
永久鏈接: http://liuqingwen.me/blog/2019/01/26/introduction-of-godot-3-part-13-the-player-jump-implementation-and-make-a-platform-game-part-2/
系列主頁: http://liuqingwen.me/blog/introduction-of-godot-series/

二、正文

本篇目標

  1. 平臺遊戲中的玩家場景
  2. 遊戲中的三種敵人實現
  3. 遊戲中的其他部分簡述
  4. 問題與總結

上一篇文中我們花了大篇幅探討平臺遊戲的最核心部分:玩家跳躍功能的實現,接下來所要討論的遊戲中其他場景相對來說就非常簡單了,特別是在結合本系列文章之前的兩個小遊戲的基礎上,我覺得對於新手而言也不會有很大的難度,而遊戲中的相關設計,額外小功能的實現,遊戲場景的豐富程度等等這些還是需要自己好好打磨的。正因爲如此,這些可以擴展、發揮的部分也就留給大家去思考實現啦,我僅僅是希望做到拋磚引玉的作用吧!

玩家場景

玩家還是那個玩家,節點還是那些節點。嗯,相比之前的遊戲,玩家場景的結構變化不大,當然也有不同之處,玩家場景中最重要的變化是多了一個非常重要的節點: Camera2D 攝像機節點。這個節點其實非常好理解,特別是在 3D 遊戲中更加常用,而且很多遊戲中會同時擁有多個攝像機。

godot_13_camera_node.png

Camera2D 節點的一些基本參數很好理解,這裏例舉幾個重要的參數說明如下:

  • Current 設置爲當前攝像頭,這個參數非常重要,不勾選攝像頭將不起作用
  • Zoom 攝像機鏡頭的縮放變焦,值越小範圍越小,圖片的顯示也就越大
  • Limit 攝像機的最大活動範圍,攝像頭移動後的位置不會超出該限制
  • Smoothing/Speed 開啓攝像頭跟隨平滑移動,可以設置移動的速度
  • Drag Margin 攝像頭不跟隨移動的尺寸範圍,超出該範圍攝像頭開始跟隨運動
  • Editor 顯示攝像機相關範圍圖形界面,不會在遊戲中顯示,用於調試

在屬性面板中,你會發現 Limit 下的默認值設置的有點荒謬,當然這在一般的遊戲中並沒有什麼問題,你也可以手動設置合理的值,把攝像頭的最大活動範圍固定在遊戲的主場景範圍內,另外 Editor 菜單中的幾個調試工具建議都開啓查看,參考上圖,其中黃色區域表示攝像機活動範圍,紫色表示屏幕尺寸或者視窗 Viewport 的大小,藍色表示玩家在此範圍內攝像機不會發生移動,超出範圍攝像機跟隨移動。

除此之外,我在攝像機節點下添加了一個帶腳本的空節點,腳本代碼簡單地實現了攝像頭抖動效果,實現原理非常簡單,有興趣的朋友可以下載源碼看看。整個玩家的場景結構如下圖:

godot_13_player_node.png

UI 場景結構圖也展示在上面了,場景的根節點是一個 CanvasLayer 節點,它的特點是能夠忽略節點的渲染排序,直接顯示在所有層的最上層,它的子節點又包含 3 個 TextureRect 節點,用於顯示玩家的血量。

在這裏我建議:如果你的 UI 場景比較簡單,完全可以把 UI 場景直接添加到玩家場景中作爲一個直接子節點,更加方便簡潔! 😃

敵人場景

這個平臺遊戲中,除了我們的主角——玩家子場景稍顯複雜外,其他稍微複雜的節點就是敵人場景了,我在遊戲中製作了 3 種行爲各異的敵人:光頭( NakedEnemy )、軍人( SoldierEnemy )以及蛇( SnakeEnemy )。

godot_13_enemy_nodes.png

從場景的結構可以看出來,其中兩個敵人的場景根節點使用的是 KinematicBody2D 節點,而怪物蛇使用的則是 Area2D 節點,它們具有的共同行爲是:左右反覆巡邏。而不同的行爲特點有這麼幾個:

  • 怪物蛇只巡邏,沒有主動攻擊的能力,場景實現代碼也是最簡單的
  • 光頭在巡邏過程中如果與玩家發生碰撞會立刻進行攻擊,之後繼續巡邏
  • 軍人無主動攻擊的能力,但會在巡邏時會往前方發射多顆子彈

怪物蛇 SnakeEnemy 場景的實現代碼最簡單,代碼如下:

extends Area2D

const UNIT = 16

export(float) var patrolRange = 1 * UNIT

onready var _sprite = $Sprite
onready var _animator = $AnimationPlayer

var _walkSpeed: float = 2 * UNIT / 2.0
var _startPosition: float = 0.0


func _ready():
    # 設置開始位置,注意爲全局位置
    _startPosition = self.global_position.x


func _process(delta):
    self.position.x += _walkSpeed * delta
    if self.global_position.x >= _startPosition + patrolRange || self.global_position.x <= _startPosition - patrolRange:
        _walkSpeed = - _walkSpeed
        _sprite.flip_h = ! _sprite.flip_h


# 發生碰撞時,檢測是否爲玩家
func _on_SnakeEnemy_body_entered(body):
    if body.is_in_group('player') && body.has_method('attacked'):
        body.attacked()


func die():
    self.set_process(false)
    _animator.current_animation = 'die'

從代碼中可以看出來,怪物蛇在遊戲中是不會檢測與地面之間發生的碰撞的,所以在遊戲關卡中,添加蛇場景的時候,必須把它的位置手動放置在地面上,否則會出現“蛇在空中移動”的現象。 😂

接着是光頭敵人 NakedEnemy 場景的實現代碼:

extends KinematicBody2D

# 省略部分代碼……

export(float) var patrolRange = 3 * UNIT # 巡邏範圍

var _walkSpeed: float = 3 * UNIT / 2.0   # 移動速度
var _gravity: float = 100                # 重力加速度
var _isStandingStill: bool = false       # 站立不動

var _startPosition: float = 0.0          # 巡邏開始位置
var _moveDirection: int = 1              # 移動方向
var velocity: Vector2 = Vector2()        # 速度
var _target = null                       # 攻擊目標:玩家


func _physics_process(delta):
    # 站着不動或者攻擊對象不爲空時,站立
    if _isStandingStill || _target != null:
        return

    velocity.y += _gravity
    velocity.x = _walkSpeed * _moveDirection
    velocity = self.move_and_slide(velocity, FLOOR_NORMAL)

    # 循環判斷碰撞體是否有玩家(**這裏不能檢測到玩家在背後的情形**)
    for index in range(self.get_slide_count()):
        var collision = self.get_slide_collision(index)
        if collision.collider.is_in_group('player'):
            _target = collision.collider
            _attack()
            return

    _animator.current_animation = _animations.walk

    if self.is_on_wall():
        _moveDirection = - _moveDirection
        _sprite.flip_h = _moveDirection != 1
    elif self.global_position.x >= _startPosition + patrolRange:
        _standStill(-1)
    elif self.global_position.x <= _startPosition - patrolRange:
        _standStill(1)


# 攻擊
func _attack():
    if 'isDead' in _target && _target.isDead:
        _target == null
        return

    if _target.has_method('attacked'):
        _target.attacked()
    _animator.current_animation = _animations.attack
    yield(_animator, 'animation_finished')
    _target = null


# 站立,nextDirection 表示站立時間過後轉身
func _standStill(nextDirection: int):
    _isStandingStill = true
    _moveDirection = nextDirection
    _animator.current_animation = _animations.idle
    _timer.start()


# 站立時間超時
func _on_StandStillTimer_timeout():
    _isStandingStill = false
    _sprite.flip_h = _moveDirection != 1

# 省略部分代碼……

這裏根節點使用 KinematicBody2D 的好處是可以調用 move_and_slide 方法移動敵人,在代碼中我給敵人添加了重力加速度,所以放置敵人的時候可以放在空中不需要貼着地面,但是這裏的重力加速度值是手動設置的,並沒有按照上一篇文章中講的那樣進行計算,也是爲了方便簡潔,夠用就可以。

軍人場景 SoldierEnemy 的代碼與 NakedEnemy 的代碼非常相似,這裏我就不貼出來了,有興趣的朋友可以到我的 Github 倉庫下載源碼查看。

遊戲中其他場景包括:攻擊效果子場景、敵人子彈、梯子、寶箱(恢復生命值)、木盒子(門)等這裏不作一一介紹,結構和代碼都很簡單,在本系列之前的文章中都陸陸續續討論過。

問題總結

總結一下這個遊戲的開發過程。其實做任何一個遊戲,不論遊戲開發有多複雜,它的結構設計都是很重要的。比如設計這麼一個簡單的平臺遊戲,你需要考慮玩家所應有的一些功能、敵人的行爲和種類、關卡地圖的設計、遊戲中互動元素的添加、甚至一些簡單的劇情等等。設計完成接下來就是考慮如何使用遊戲引擎來完成製作了。

本次小遊戲的結構設計可以參考下圖:

godot_star_hunter_scenes.png

這張圖片場景名稱並不相同,它是另一個遊戲的場景結構,圖片來源於 Thingsmatic 的一篇博客文章: Making a 2D platform game with Godot 3.0 ,儘管場景名字不同,但是總體結構是相似的,大家可以參考參考。

再來說說本次 Demo 中的一個非常重要的問題或者 BUG :玩家在敵人( KinematicBody2D )身後挨着走也不會受到攻擊!也就是說,玩家可以從背後推着敵人走而敵人並不能檢測到與玩家之間的碰撞,從而玩家可以安然無恙地“躲過一劫”!

kinematicbody2d_collision_test.gif

如何解決這個問題呢?至少有三種最直接的方式:

  1. 添加 Area2D 子節點用於檢測碰撞
  2. 使用 Raycast 射線節點檢測碰撞
  3. 使用 Physics2DDirectSpaceState.intersect_ray() 方法檢測碰撞

爲了讓遊戲更加簡單易懂,我並沒有在遊戲中解決這個 BUG ,不過我會在後續文章中討論這個問題,以及遊戲中常用的 FSM 有限狀態機制我想也會談到,有興趣的朋友可以自己先探索。 😃

三、總結

小遊戲算是完成了,但是還有太多不足之處,大家可以充分發揮自己的聰明才智,把你心目中的遊戲打造的更加好玩、更加有特色,這裏我能想到的一些可以在 Godot 中爲遊戲增光添彩的功能有:

  • 添加單邊碰撞體作爲地面
  • 玩家移動時的灰塵特效
  • 給玩家添加無敵狀態
  • 添加空中移動平臺
  • 給敵人添加 AI
  • 更多交互元素、更好的關卡設計等

本篇的 Demo 以及相關代碼已經上傳到 Github ,地址: https://github.com/spkingr/Godot-Demos , 這應該是春節前的最後一篇文章,原創不易,希望大家喜歡,我們 2019 新年見! 😄

我的博客地址: http://liuqingwen.me
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐: https://cloud.tencent.com/developer/support-plan?invite_code=3sg12o13bvwgc
歡迎關注我的微信公衆號:
IT自學不成才

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章