Godot Engine

เขียนเกม 2D ด้วย Godot Engine จัดการ Camera2D และยิง Bullet

บทเรียนการสร้างเกม 2D ด้วย Godot Engine ส่วนของการทำให้ Camera2D ติดตามตัวละคร และ การทำ Instance Singleton สำหรับยิงกระสุนเรียก xml ของ Bullet

หมายเหตุ: กรุณาศึกษาบทเรียนก่อนหน้านี้:

บทเรียนต่อจากนี้จะเป็นการต่อยอด จาก บทเรียนก่อนหน้า ดังนั้นให้ทำการเปิด player.gd ขึ้นมาเพื่อ cleanup code เล็กน้อย คือการประกาศตัวแปรเพิ่ม

var anim
var isShootPressed = false
var fire_rate = 0.0
var next_fire = 0.5

แล้วประกาศใน func _ready():

anim = get_node( "AnimatedSprite" )

และแก้ไขบรรทัด ทั้งหมดที่เป็น

get_node( "AnimatedSprite" ).play("ทุกอย่าง")

เป็น

anim.play("ทุกอย่าง")

เมื่อพร้อมแล้ว เราก็จะเริ่มสร้าง Camera2D มาทำการวิ่งตามตัวละครก่อน ให้เรา Add Child Node ของ Player เลือก Camera2D เข้าไป

เพิ่ม Camera2D เข้าไปใน Player

ทำการคลิกที่ Current ใน Inspector ของ Camera พร้อมกำหนด Offset ของกล้องเล็กน้อยเป็นขนาดที่เราต้องการ

ตอนนี้หากทดสอบจะเห็นว่ากล้องวิ่งตามตัวละครเรียบร้อยแล้ว หมดเรื่องของ Camara2D แล้วต่อไปคือเรื่องของกระสุน ซึ่งอย่างที่บอก Godot จะทำงานเป็น Node และ instance Node เราจะต้องเซฟ Scene นี้ไว้แล้วทำการ New Scene ขึ้นมาใหม่ วาง KinematicBody2D ลงไป ตามด้วย Sprite และ CollisionShape2D

ลาก Sprite ของกระสุนไปใช้ใน Sprite

กำหนดค่า CollisionShape2D เลือก Shape เป็น Circle

ปรับ Trigger ของ CollisionShape2D ใน Inspector เป็น on เพื่อใช้สำหรับการชนกับศัตรู หรือบางอย่าง (เหมือน Unity นั่นแหละ)

หลังจากนั้นเซฟ Save as new Scene ว่า bullet เสียแต่ ให้ใจเย็นๆ แล้วเลือก Scene Type เป็น .xml นะครับ ให้สังเกตว่า เราจะต้อง ได้ bullet.xml นะครับ ไม่ใช่ bullet.tscn

หลังจากนั้นให้เราประกาศคำสั่งต่อจากนี้ลงใน player.gd ของเรา ประกาศไว้ใต้ extend

extends KinematicBody2D
var bullet = preload("res://bullet.xml")

เพื่อที่เราจะโหลด XML ที่เป็น node 2D แบบ KinematicBody2D ของ bullet ที่เราสร้างไว้มาใช้ได้เลย เก็บลงตัวแปร bullet

สร้าง Scene ใหม่ขึ้นมาชื่อว่า global.gd ให้เราใส่แค่ Node2D เปล่าๆ ไว้ 1 ตัวแล้วอัก Script ดังนี้ลงไป

extends Node2D
var global_direction = 0

เจ้า node2D ของ global ที่มีตัวแปร global_direction นี่จะเป็น node กลางที่ตัวแปร global_direction นี้จะใช้ได้กับทุก node ทั้ง player.gd และ ไฟล์ gd อื่นๆ เพียงแค่เราต้องใช้ singletons, รูปแบบการออกแบบภาษาโปรแกรมที่จำกัดจำนวนของ Object ที่ถูกสร้างขึ้นในระบบ ซึ่งจะเป็นประโยชน์เมื่อระบบต้องการจะมี Object นั้นเพียงตัวเดียวเพื่อป้องกันไม่ให้เกิดการทำงานซ้ำซ้อนกันเช่น class สำหรับการเก็บข้อมูล หรือเป็น Model ที่มีการเรียกใช้งานทั้งระบบนั่นเอง

ไปที่ เมนู Scene เลือก Project Setting

เลือก global.gd เป็น Singletons

หลังจากนี้เราจะใช้ ตัวแปร global_direction จาก global.gd ได้ทุก node อย่างทั่วถึงแล้ว ซึ่ง global_direction นี้จะทำการเก็บ input_direction ของ player ว่าหันไปทางไหน ซ้าย หรือ ขวา เพื่อที่กระสุนจะได้พุ่งออกไปยังทิศนั้นแค่นั้นแหละครับ (ปล. Code ผมมันลูกทุ่งหน่อย)

ดังนั้นปรับฟังก์ชันเพิ่มของ player.gd สักหน่อย ใน func _process(delta):

#Input
	if input_direction:
		direction = input_direction
                global.global_direction = input_direction

และเพิ่มคำสั่งการยิง

#Fire
	if Input.is_key_pressed(KEY_Z):
		anim.play("attack")
		if isShootPressed == false:
			fire()
			isShootPressed = true
	
	#Check FireRate
	fire_rate += delta
	if fire_rate >= next_fire:
		fire_rate = 0.0
		isShootPressed = false	
		anim.play("Idle")

ไม่มีอะไรมากแค่กด Z ก็จะเรียกฟังก์ชัน fire() ที่เราสร้างขึ้นมาเอง โดยกำหนดอัตราการยิง หาก fire_rate น้อยกว่า next_fire ที่เรากำหนดไว้คือ 0.5 ยิงได้ ถ้าไม่ก็ยิงไม่ออก กันการกดยิงรัวๆ แค่นั้นแหละ anim.play(“attack”) รู้ใช่ไหมว่าเราต้องเพิ่ม Animation อะไรเข้าไป ก็ attack นั่นเอง (ตัด Photoshop เหนื่อยมาก)

ทีนี้เรามาเขียน function ใหม่กันคือ fire()

func fire():
		var bullet_instance = bullet.instance()
		bullet_instance.set_name("bullet(Clone)")
		add_child(bullet_instance)

ง่ายๆ เลยให้สร้าง instance คือเจ้า bullet (ซึ่งก็คือ Scene bullet.xml) แล้วตั้งชื่อมันใหม่ว่า bullet(Clone) แล้วทำการ add_child() ติดกับผู้เล่นซะ

ภาพรวมของ player.gd จะเป็นดังนี้:

extends KinematicBody2D
var bullet = preload("res://bullet.xml") 

var input_direction = 0
var direction = 0
var speed = 0
var velocity = 1

const MAX_SPEED = 600
const ACCELERATION = 1000
const DECELERATION = 2000
const JUMP_FORCE = 20.0

var anim
var isShootPressed = false
var fire_rate = 0.0
var next_fire = 0.5

func _ready():
	set_process(true)
	anim = get_node( "AnimatedSprite" )
	pass

func _process(delta):
	#SeT Gravity
	move( Vector2(0,10))
	
	#Input
	if input_direction:
		direction = input_direction
		global.global_direction = input_direction
		
	if Input.is_action_pressed("ui_left"):
		input_direction = -1
		anim.set_flip_h( true )
		anim.play("walk")		
	elif Input.is_action_pressed("ui_right"):
		input_direction = 1
		anim.set_flip_h( false )
		anim.play("walk")		
	else:
		input_direction = 0
		anim.play("Idle")
	#Check Fire Animation
	
	
	# Movement
	if input_direction == - direction:
		speed /= 3

	if input_direction:
		speed += ACCELERATION * delta
	else:
		speed -= DECELERATION * delta

	speed = clamp(speed, 0, MAX_SPEED)	
	velocity = speed * delta * direction
	move(Vector2(velocity, 0))
	
	#Splash Attack
	if Input.is_key_pressed(KEY_X):
		anim.play("attack")
		move(Vector2(20 * input_direction, 0))
	
	#Fire
	if Input.is_key_pressed(KEY_Z):
		anim.play("attack")
		if isShootPressed == false:
			fire()
			isShootPressed = true
	
	#Check FireRate
	fire_rate += delta
	if fire_rate >= next_fire:
		fire_rate = 0.0
		isShootPressed = false	
		anim.play("Idle")
	
	#Jump
	if Input.is_action_pressed("ui_up"):
		anim.play("jump")
		move(Vector2(0, -JUMP_FORCE))
	pass
	
func fire():
		var bullet_instance = bullet.instance()
		bullet_instance.set_name("bullet(Clone)")
		add_child(bullet_instance)

ทีนี้ไปที่ Scene ของ Bullet.xml ให้เรา Attach Script เข้าไปที่ KinematicBody2D ของ Bullet ตั้งชื่อว่า bullet.gd เขียน code ดังนี้:

extends KinematicBody2D

var bulletSprite
var wait_time = 0.0
var end_time = 0.3

func _ready():
	set_process(true)
	bulletSprite = get_node( "Sprite" )
	pass

func _process(delta):
	var bulletDirection = global.global_direction
	if bulletDirection == -1:
		bulletSprite.set_flip_h( true )
		translate(Vector2(-20,0))
	else:
		bulletSprite.set_flip_h( false )
		translate(Vector2(20,0))
	
	wait_time += delta
	if wait_time > end_time:
		wait_time = 0.0
		destroy()

func destroy():
    queue_free()

สังเกตสิ:

var bulletDirection = global.global_direction

คือไปเรียก global.gd เอาตัวแปร global_direction มาใช้นั่นเอง

ส่วนการทำงานง่ายๆ กระสุนมีอายุแค่ 0.3 วินาที ถ้าเกินนั้นให้ทำลายตัวเอง เรียกฟังก์ชัน destroy() ส่วนคำสั่ง delete_node นั้นใช้ queue_free() เป็นคำสั่งมาตราฐานเลย

เอ้าทดสอบ

คิดว่าคงได้ประโยชน์กันขอ จบส่วนของ Character ไว้เท่านี้ก่อน จะทำ Repo ไว้ให้ เพราะหลังจากบทนี้จะเป็นการทำ AI Navmesh ของ godot Engine ซึ่งขอเวลาไปศึกษาก่อนนะครับ

Download Tutorial Source: https://github.com/banyapondpu/godot_character_daydev

Asst. Prof. Banyapon Poolsawas

อาจารย์ประจำสาขาวิชาการออกแบบเชิงโต้ตอบ และการพัฒนาเกม วิทยาลัยครีเอทีฟดีไซน์ & เอ็นเตอร์เทนเมนต์เทคโนโลยี มหาวิทยาลัยธุรกิจบัณฑิตย์ ผู้ก่อตั้ง บริษัท Daydev Co., Ltd, (เดย์เดฟ จำกัด)

Related Articles

Back to top button

Adblock Detected

เราตรวจพบว่าคุณใช้ Adblock บนบราวเซอร์ของคุณ,กรุณาปิดระบบ Adblock ก่อนเข้าอ่าน Content ของเรานะครับ, ถือว่าช่วยเหลือกัน