FeaturedGame DevelopmentUnity 3D

เขียนเกม 3 มิติด้วย Unity การสร้างระบบ Melee Combat แบบ Noob

บทเรียน Noob หน่อยครับเป็นการสร้างระบบ Melee Combat เบื้องต้นแบบง่ายๆ พร้อม Logic ของศัตรูหรือระบบ AI ในการเข้ามาต่อสู้ประชิดตัวกับเราด้วย Unity แบบง่ายครับ

ก่อนจะเริ่มต้นยังไงก็ไปศึกษาการเขียนเกมด้วย Unity ให้เสร็จสรรพก่อนนะครับจะได้ไม่งง และช่วงหลังเราจะเปลี่ยนจาก Javascript เป็น C# เป็นหลักครับ (อันที่จริงใช้ด้วยกันได้ เขียนเหมือนกันต่างแค่ตอนประกาศตัวแปร และบางคำสั่ง)

[บทเรียนย้อนหลัง: เขียนเกมด้วย Unity]

ให้ศึกษาบทเรียนก่อนหน้าก่อนเรื่องของการ สร้าง Animator Controller เบื้องต้นเกี่ยวกับ State ของการเคลื่อนไหว Animation ต่างๆ ครับ

ตัวอย่างนี้ผมจะสร้าง Character ของผมเองขึ้นมาก่อนคือ BoonMee แต่จะไม่ใช้ระบบ Animation แบบ Legacy แล้วจะเป็น Mecanim แทน

unity-melee-combat-1

วางตัวละครของเราลงไปในฉากของ Scene View ครับ ทำการสร้าง Character Controller, Capsule Collider และ RigidBody ให้เรียบร้อยและกำหนดค่าตามตัวอย่างครับ ตามด้วยเอา Main Camera ไปไว้ใน Layer ของ Player บน Hierachy

unity-melee-combat-2

ต่อมาให้ Select และทำการ Rigged ตัวโมเดลเป็น Humanoid แล้วสร้าง Animator Controller ขึ้นมา ตามบทเรียนก่อนหน้า

กำหนด Parameter ตามนี้ โดย State ทั้งหมดคือ ยืน->วิ่ง->กระโดด->โจมตี ตามภาพ

unity-melee-combat-3

ตัวเส้น Transition นั้นมีการ เรียก Condition ตามชื่อตัวแปรครับ เช่น

Idle -> Run ก็ใช้ IsWalking = true และ Run -> Idle ก็ใช้ IsWalking = false

ส่วนของการตาย ก็ใช้ Idle -> Dying เลือก Death เช่นกัน การโจมตีก็ใช้ Bool เป็น Attack สำหรับการ ง้างค้อนโจมตี (คือการตั้ง Parameter ในบทเรียนนี้ มันเป็นอะไรที่ตั้งชื่อตัวแปรแบบ clean และง่ายๆ อีกทั้ง Make Sense สุดๆครับ ถ้ายังมีคนไม่เข้าใจ แล้วยังถามจุดนี้ ผมจะตอบว่า ให้ไปดูบทเรียนก่อนหน้า: เขียนเกม 3 มิติด้วย Unity การใช้ Animator Controller หรือไม่ก็คงต้องตอบว่า ไม่ต้องทำหรอก! ทำไปทำอย่างอื่นเถอะครับ)

ออกแบบ ฉากให้มีพื้น และ Plane เป็นน้ำทะเลตั้งชื่อ “Sea” บท Inspector

unity-melee-combat-4

 

ตรวจสอบเรื่อง Trigger กันดีๆ นะครับ

สร้างไฟล์ C# ขึ้นมาครับ ชื่อว่า Player.cs ใช้กับตัวละครของเรา

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
	Animator anim;
	public float speed = 6.0F;
	public float jumpSpeed = 12.0F;
	public float gravity = 13.0F;
	private Vector3 moveDirection = Vector3.zero;
	public bool Running = false;
	public bool Jumping = false;
	public bool Death = false;

	public float rotationSpeed = 100.0F;

	void Start(){
		anim = GetComponent <Animator> ();
		Time.timeScale = 1;
	}

	void Update() {

		CharacterController controller = GetComponent<CharacterController>();
		if (controller.isGrounded) {
			moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
			moveDirection = transform.TransformDirection(moveDirection);
			moveDirection *= speed;

			if (Input.GetButton("Jump")){
				moveDirection.y = jumpSpeed;
				Jumping = true;
			}else{
				Jumping = false;
			}
			if (Input.GetKey((KeyCode.D)) 
			    || Input.GetKey("right") 
			    || Input.GetKey((KeyCode.A)) 
			    || Input.GetKey("left")
			    || Input.GetKey((KeyCode.D)) 
			    || Input.GetKey("up")
			    || Input.GetKey((KeyCode.W)) 
			    || Input.GetKey("down")
			    || Input.GetKey((KeyCode.S))
			    ){
				Running = true;

			}else{
				Running = false;
			}

			if(Running == true){
				//anim.SetTrigger("Running");
				anim.SetBool ("IsWalking", true);
			}else{
				anim.SetBool ("IsWalking", false);
			}

			if(Jumping == true){
				//anim.SetTrigger("Running");
				anim.SetBool ("IsJumping", true);
			}else{
				anim.SetBool ("IsJumping", false);
			}

		}

		//Attack
		if (Input.GetMouseButtonDown (0)) {
			anim.SetBool ("Attack", true);
			//Debug.Log ("Attack");
		} else {
			anim.SetBool ("Attack", false);
		}

		//Mouse movement
		float translation = Input.GetAxis("Vertical") * speed;
		float rotation = Input.GetAxis("Horizontal") * rotationSpeed;
		translation *= Time.deltaTime;
		rotation *= Time.deltaTime;
		transform.Translate(0, 0, translation);
		transform.Rotate(0, rotation, 0);


		moveDirection.y -= gravity * Time.deltaTime;
		controller.Move(moveDirection * Time.deltaTime);
	}

	void OnTriggerEnter(Collider theCollision){
		if (theCollision.gameObject.name == "Sea") {
			Death = true;
			
			if (Death == true) {
				anim.SetTrigger ("Death");
			}
			//Application.LoadLevel ("Stage1");
		} else {
			Death = false;
		}
	}
}

ลอง Run แล้ววิ่งตกน้ำดูสักที ถ้าตายแปลว่าโอเค ไม่ก็ลองวิ่งแล้วกระโดด หรือ คลิกเมาส์โจมตีครับ

ทีนี้ อาวุธที่อยู่มือขวานี่ ผมจะใช้วิธี Noob ซึ่งเทคนิคนี้แล้วแต่คน หรือแล้วแต่รูปทรงของ โมเดลนะครับ จะใช้ Mesh Collider ก็ได้ครับง่ายด้วยแต่พอศัตรูเดินมาชนเรายังไม่ทันกดตี มันโดนอาวุธเราก็ตายซะและ เลยใช้เทคนิดนี้ครับ

ออกแบบ Sphere Collider และ Set Trigger ให้มันเป็นแบบตัวอย่างในรูปครับ

unity-melee-combat-5

ที่วางตำแหน่งนั้น ก็เพื่อให้มันหมุนเวลาเกิดการเหวี่ยงค้อนไปอยู่บนหัวของศัตรูทีละครั้งครับ เหมือน ค้อนเป็นแค่ การหลอก แต่ตัวที่ทำเลือดลดจริงๆ คือ Sphere Collider ครับ

ดูการเหวี่ยงนะครับ

unity-melee-combat-6

 

ระหว่างที่ค้อนเหวี่ยง sphere จะถูกเด้งไปข้างหน้าข้างหลังไปโดนศัตรูตามจำนวนครั้งที่ทุบครับ

ต่อมาเรามาสร้าง ศัตรูดีกว่า วางโมเดลลงไปครับ สร้าง Character Controller, RigidBody, Capsule Collider เหมือนตัว Player ของเราได้เลย

unity-melee-combat-7

ออกแบบ Animator controller ดังนี้ครับ เพื่อใช้กับ Mecanim ตามตัวอย่าง

unity-melee-combat-8

สังเกต Parameter ดีๆ นะครับ คงจะไม่อธิบายแล้ว นำไปใช้สร้าง Transition กันได้เลย ตัวแปร Make Sense เข้าใจง่ายสุดๆ

สร้าง C# ไฟล์ขึ้นมาตั้งชื่อว่า Enemy.cs ครับ เขียนคำสั่งตามนี้

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {
	public float Distance;
	public Transform Target;
	float lookAtDistance = 10.0f;
	float HitAtDistance = 1.0f;
	float attackRange = 5.0f;
	float moveSpeed = 3.0f;
	float Damping = 6.0f;
	public int Health = 30;
	Animator anim;
	public bool dead = false; 

	// Use this for initialization
	void Start () {
		anim = GetComponent <Animator> ();

	}
	
	// Update is called once per frame
	void Update () {
		Distance = Vector3.Distance(Target.position, transform.position);

		if(Distance < lookAtDistance){
			var rotation = Quaternion.LookRotation(Target.position - transform.position);
			transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * Damping);
		}

		if (Distance > lookAtDistance) {
			//Idle
			anim.SetBool ("FoundPlayer", false);
			anim.SetBool ("AttackPlayer", false);
		} else {
			//Turn to Player
			var rotation = Quaternion.LookRotation(Target.position - transform.position);
			transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * Damping);
		}

		if (Distance < (attackRange + 0.9) && Distance > 0.95f) {
			anim.SetBool ("FoundPlayer", true);
			transform.Translate (Vector3.forward * moveSpeed * Time.deltaTime);
		} else {
			anim.SetBool ("FoundPlayer", false);
			anim.SetBool ("AttackPlayer", true);
		}

		if (Distance <= 0.95f) {
			anim.SetBool ("AttackPlayer", true);
		} else {
			anim.SetBool ("AttackPlayer", false);
		}


		if (Health <= 0) {
			dead = true;
		} else {
			dead = false;
		}

		if (dead == true) {
			death();
		} 
	}

	void OnTriggerEnter(Collider theCollision){
		if (theCollision.gameObject.name == "Weapon") {
			Health = Health-2;
			Debug.Log ("HP: "+Health);
			//Health
		}
	}

	void death(){
		lookAtDistance = 0.0f;
		HitAtDistance = 0.0f;
		attackRange = 0.0f;
		anim.SetBool ("AttackPlayer", false);
		anim.SetBool ("FoundPlayer", false);
		anim.SetTrigger ("Died");
	}
}

เราจะสร้างให้ศัตรูมองตามเราก่อน เมื่อเราเข้าใกล้ระยะ 10.0f ตามตัวแปร lookAtdistance ลองเดินไประยะ 10.0f โดยประมาณครับ มันจะหันมามองเราแบบหลอนๆ

unity-melee-combat-9

 

จ้องหลอนๆ

สังเกตในคำสั่งนี้จะเป็นการบอกให้ว่าถ้าเราเข้าใกล้มันเกินระยะโจมตี มันจะวิ่งมาหาเราแล้ว พักระยะไว้ 0.9f แล้วเปลียนสถานะเป็น Attack ใส่เราครับ

if (Distance < (attackRange + 0.9) && Distance > 0.95f) {
			anim.SetBool ("FoundPlayer", true);
			transform.Translate (Vector3.forward * moveSpeed * Time.deltaTime);
		} else {
			anim.SetBool ("FoundPlayer", false);
			anim.SetBool ("AttackPlayer", true);
		}

unity-melee-combat-10

ผมได้เขียน ฟังก์ชัน OnTriggerEnter() ไว้ให้แล้วว่าถ้า ศัตรูถูก Sphere Collider ของเราฟาดเอาพลังมันจะลด ถ้าพลังหมด มันจะ Trigger Animator เป็น Died ครับ

void OnTriggerEnter(Collider theCollision){
		if (theCollision.gameObject.name == "Weapon") {
			Health = Health-2;
			Debug.Log ("HP: "+Health);
			//Health
		}
	}

และ

void death(){
		lookAtDistance = 0.0f;
		HitAtDistance = 0.0f;
		attackRange = 0.0f;
		anim.SetBool ("AttackPlayer", false);
		anim.SetBool ("FoundPlayer", false);
		anim.SetTrigger ("Died");
	}

ลองคลิกเมาส์สู้กับมัน โจมตีไปเรื่อยๆ

unity-melee-combat-12

 

หมีนอนตายแน่นิ่งละ

unity-melee-combat-13จบครับบทเรียนนี้ Noob พอไหม แต่ก็ใช้ได้ครับเอาไปประยุกต์ใช้ได้กับหลายๆ เกมได้อย่างสบายๆ และ Algorithm ต่างๆ ก็ประยุกต์จาก Document ของ Unity เท่านั้นส่วน Logic ก็คิดเองครับ ดังนั้นหากใครคิด Logic ได้ดีกว่าก็แลกเปลี่ยนความรู้ได้ครับ

Source Code: เนื่องจาก Asset ทั้งหลายผมมีการจ่ายเงินซื้อมาดังนั้นให้ไม่ได้ครับ รบกวนศึกษากันอย่างเดียวละกัน

 

Asst. Prof. Banyapon Poolsawas

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

Related Articles

Back to top button

Adblock Detected

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