DeveloperFeaturedGame DevelopmentUnity 3D

สร้างเกมบน Unity 3D กับชุดพัฒนาเกมสำเร็จรูป Daydev Action Kit

บทความฉบับเร่งรัดสำหรับผู้ต้องการสร้างเกม 3D แนว Action มุมมองบุคคลที่ 3 ด้วย Unity ครับ สำหรับผู้ที่ต้องการไอเดียการพัฒนาเกม

อันที่จริงนี่เป็นหนึ่งในเครื่องมือประกอบรายละเอียดงานวิจัยในห้องเรียน มหาวิทยาลัยธุรกิจบัณฑิตย์ คณะเทคโนโลยีสารสนเทศ สาขาวิชาออกแบบเชิงโต้ตอบ และพัฒนาเกมที่ผมกำลังทำอยู่เกี่ยวกับการเรียนภาษาโปรแกรม กับการพัฒนาตัว และทัศนคติของผู้เรียนการเขียนโปรแกรมที่มีต่อเครื่องมือ Game Creator Template ที่ทางผมพัฒนาขึ้น เพื่อศึกษาการเขียนโปรแกรมแบบ Backward (สนใจสอบถาม หรือรีวิวรายละเอียดเรื่องงานวิจัยทักได้ที่ Fan Page ครับ)

เบื้องต้นนั้นผมได้ทำ Package ของ Unity ออกมาให้แล้วสำหรับคนที่ต้องการใช้เป็นตัวอย่างในการพัฒนาเกม 3 มิติ โดย Asset ที่ผมใช้มีทั้งตัวจ่ายเงิน คือ Animal Adventrue Pack (10$) และ Cartoon City (5$) ส่วนนี้ขอความกรุณานำไปใช้เพื่อการศึกษาอย่าได้นำไปใช้เชิงพาณิชย์ เลยนะครับ

Screen Shot 2558-06-06 at 3.17.04 PM

Screen Shot 2558-06-06 at 4.51.55 PM

ตัว Package นั้นผมได้อัพโหลดไว้ที่

http://bit.ly/DAYDEVKIT

[เป็นเวอร์ชัน 1 ที่ยังไม่ได้ใส่ส่วนของ เสียงไว้ให้ครับ รองรับ Unity version 5 ขึ้นไปเท่านั้นนะครับ]

วิธีการนำ Package Daydev Action Kit ไปใช้

ขั้นตอนแรกให้ คลาย zip ออกมาเป็น package ของ Unity ครับ

Screen Shot 2558-06-06 at 4.47.58 PM

เสร็จแล้วเปิดโปรแกรม Unity ขึ้นมา (เวอร์ชัน 5) เท่านั้น สร้าง Project ใหม่ขึ้นมาครับ

Screen Shot 2558-06-06 at 4.47.41 PM

ไปที่เมนู Asset -> Import Package -> Custom Package…

Screen Shot 2558-06-06 at 4.47.49 PM

เลือกไฟล์ Daydev Action Kit ที่เป็น Package ที่เราโหลดมาใส่เข้าไป รอสักพักครับ ก็เป็นอันเรียบร้อย

Screen Shot 2558-06-06 at 4.48.11 PM

ลองตรวจสอบเราจะเห็นว่า โฟลเดอร์ Prefabs นั้นจะมี สิ่งพร้อมใช้ให้ลากวางก็กลายเป็นเกมๆ หนึ่งได้เลยครับ

Screen Shot 2558-06-06 at 4.47.17 PM

ใน โฟลเดอร์ Scene ก็จะมี ด่านตัวอย่างที่ทำไว้ให้เรียบร้อย วิธีการก็คือ ลาก Prefabs ไปวางทีละส่วนๆ นั่นแหละครับ แต่ผมทำไว้ให้แล้ว

Screen Shot 2558-06-06 at 4.47.01 PM

เปิด Scene ที่ชื่อ Example01 ได้เลยครับ

Screen Shot 2558-06-06 at 4.47.06 PM

ตัวละครจะพร้อมใช้งาน หรือพร้อมเล่นเลย

ส่วนประกอบต่างๆ และการทำงาน

Player ของตัวละครจะใช้ Code player.cs ครับ ซึ่งจะมี Code ดังนี้

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 ParticleEmitter waterDeath;
	public ParticleEmitter deathLight;
	public ParticleEmitter AttackEffect;
	public float fireRate = 0.3f;
	private float nextFire = 0.0f;
	//Mouse
	public float rotationSpeed = 100.0F;
	public float horizontalSpeed = 2.0F;
	public float verticalSpeed = 2.0F;
	//Health
	public GUISkin HealthBarSkin;
	public int healthBar = 500;
	public bool isWinner = false;


	public bool isGameOver = false;

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


	public void Winning()
	{
		Debug.Log("Winn!!!!!!");
		isWinner = true;
	}

	
	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);
			}
			
		}

		float h = horizontalSpeed * Input.GetAxis("Mouse X");
		transform.Rotate(0, h, 0);
		
		//Attack
		if (Input.GetMouseButtonDown (0)&& Time.time > nextFire) {
			nextFire = Time.time + fireRate;
			anim.SetBool ("IsAttack", true);
			//Debug.Log ("Attack");
		} else {
			anim.SetBool ("IsAttack", 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");
				Instantiate(waterDeath, transform.position, transform.rotation);
				//Destroy(gameObject);
				StartCoroutine(WaitDeath());
			}
			//Application.LoadLevel ("Stage1");
		} else {
			Death = false;
		}

		if (theCollision.gameObject.name == "Monsters") {
			healthBar-=20;
			
			if(healthBar <=0){
				healthBar = 0;
				anim.SetTrigger ("Death");
				StartCoroutine(WaitDeath());

			}
		}
		if (theCollision.gameObject.name == "hits") {
			healthBar-=10;
			
			if(healthBar <=0){
				healthBar = 0;
				anim.SetTrigger ("Death");
				StartCoroutine(WaitDeath());
				
			}
		}
		if (theCollision.gameObject.name == "hitsboss") {
			healthBar-=140;
			
			if(healthBar <=0){
				healthBar = 0;
				anim.SetTrigger ("Death");
				StartCoroutine(WaitDeath());
				
			}
		}
		if (theCollision.gameObject.name == "BossBullet(Clone)") {
			Instantiate(AttackEffect, transform.position, transform.rotation);
			healthBar-=70;
			
			if(healthBar <=0){
				healthBar = 0;
				anim.SetTrigger ("Death");
				StartCoroutine(WaitDeath());
				
			}
		}

		if (theCollision.gameObject.name == "WinCondition") {
			
			Winning ();
		}
	}

	IEnumerator WaitDeath() {
		Instantiate(deathLight, transform.position, transform.rotation);
		yield return new WaitForSeconds(2);
		isGameOver = true;
	}


	void OnGUI(){


		if (isWinner == true) {
			Time.timeScale = 0;
			//Show Total Score
			GUI.Box(new Rect(Screen.width/4, 
			                 Screen.height/4+10, 
			                 Screen.width/2, 
			                 Screen.height/2), 
			        "Win!!!!: ");
			
			//Restart Game
			if (GUI.Button(new Rect(Screen.width/4+10, 
			                        Screen.height/4+Screen.height/10+50, 
			                        Screen.width/2-20, Screen.height/10), 
			               "Next Stage")){
				Application.LoadLevel(Application.loadedLevel);
			}
		}


		//int IntScore = score;
		if(!isGameOver){
			
			GUI.skin = HealthBarSkin;
			GUI.Label(new Rect(Screen.width-(Screen.width-35), 32, Screen.width/6, 
			                   Screen.height/6),
			          "HP: "/*+IntScore.ToString()*/);

			GUI.Label(new Rect(Screen.width/2, Screen.height/2-25, 100, 50),"+");
			
			
			GUI.backgroundColor = Color.yellow;
			GUI.Button(new Rect(Screen.width-(Screen.width-35),12,(healthBar/2),20),"");
			
		}else{
			Time.timeScale = 0;
			//Show Total Score
			GUI.Box(new Rect(Screen.width/4, 
			                 Screen.height/4+10, 
			                 Screen.width/2, 
			                 Screen.height/2), 
			        "GAME OVER: ");
			
			//Restart Game
			if (GUI.Button(new Rect(Screen.width/4+10, 
			                        Screen.height/4+Screen.height/10+50, 
			                        Screen.width/2-20, Screen.height/10), 
			               "RESTART")){
				Application.LoadLevel(Application.loadedLevel);
			}
		}
	}
}

มีการชนโดน วัตถุต่างๆ แล้วเกิดเป็น ตัวแปล healthBar ลดไป แต่ถ้าชนสิ่งที่ชื่อว่า “Wincondition” หรือมรกต สีเขียวก็จะผ่านด่านเลย (ซึ่งผ่านด่านก็คือ เล่นใหม่ไม่ได้ทำด่านต่อ)

Screen Shot 2558-06-06 at 8.00.55 PM

ส่วนของการบังคับตัวละครอธิบายมาหลายบทในเว็บไซต์แล้วขอ ละไว้ไปหาอ่านกันเองไม่ยากครับ มาส่วนของ Effect กันดีกว่า

public ParticleEmitter waterDeath;
public ParticleEmitter deathLight;
public ParticleEmitter AttackEffect;

เมื่อตกน้ำตาย จะมี Effect ตัวแปร waterDeath ให้ดึง Prefabs ที่ชื่อ Water มาวางเพื่อเกิดเป็นน้ำกระจาย เวลาตกลงไป

Screen Shot 2558-06-06 at 8.02.29 PM

เช่นกันเมื่อ เลือดหมด Effect ที่ชื่อ deathLight แสงแห่งสวรรค์ก็จะปรากฏเมื่อตายเช่นกันครับ ตามด้วย AttackEffect เป็น Effect เมื่อโดนโจมตีโดยศัตรูนั่นคือ Monster และ Boss ครับ

Monster
Monster
Boss
Boss

ทีนี้ตัวละครของเรา ใน Hierachy จะมี Cube ที่ปรับ Mesh Renderer ให้ล่องหนอยู่ตัวหนึ่งทำหน้าที่เป็น ปืนครับ

Screen Shot 2558-06-06 at 8.04.25 PM

มันคือ Cube ธรรมดาๆ แสน ธรรมดาที่ปรับ Inspector เป็นดังนี้

Screen Shot 2558-06-06 at 8.05.06 PM

นึกไม่ออกให้ Double Click ที่ gun ใน Hierarchy ดูครับ

Screen Shot 2558-06-06 at 8.06.03 PM

ใช้ Script  ที่ชื่อ gun.cs ไปควบคุมมันอีกทีครับ

using UnityEngine;
using System.Collections;

public class Gun : MonoBehaviour {
	public GameObject Bullet;
	public float fireRate = 0.3f;
	private float nextFire = 0.0f;

	// Use this for initialization
	void Fire () {
		Instantiate(Bullet, transform.position, transform.rotation);
	
	}
	
	// Update is called once per frame
	void Update () {
		if (Input.GetButtonDown("Fire1") && Time.time > nextFire) {
			nextFire = Time.time + fireRate;
			Fire();
		} 

โดยเราต้องมีกระสุนผมได้สร้าง Prefab เป็นกระสุนไว้ให้แล้ว

Screen Shot 2558-06-06 at 8.08.37 PM

(สามารถลาก Prefabs ไปวางที่ Inspecter ของ Gun ได้ครับถ้าทำกระสุนใหม่)

Screen Shot 2558-06-06 at 8.08.52 PM

ส่วนของ Bullet มี Code ตามนี้ครับ เน้น RigidBody เมื่อยิงมันจะย้อยลงดินหายไป ส่วน gun.cs จะมีการ Cool down ไม่ให้ยิงรัวๆ ประมาณ 3 วินาที

using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {
	float Speed = 0.7f;
	float SecondsUntilDestroy = 1.2f;
	float startTime;
	public ParticleEmitter ClearBullets;

	// Use this for initialization
	void Start () {
		startTime = Time.time;
	}
	
	// Update is called once per frame
	void Update () {
	
	}

	void FixedUpdate(){
		this.gameObject.transform.position += Speed * this.gameObject.transform.forward;
		if (Time.time - startTime >= SecondsUntilDestroy) {
			Instantiate(ClearBullets, transform.position, transform.rotation);
			Destroy(this.gameObject);
		}
	}

	void OnTriggerEnter(Collider collision){
		if(collision.gameObject.name == "Monsters(Clone)"){
			Destroy(gameObject);
		}
	}
}

เมื่อมันโดน GameObject ที่ชื่อ Monsters(Clone) มันจะทำลายตัวเอง และถ้าไม่โดนอะไร จะมีการเคลียร์วัตถุไม่ให้เปลืองแรม SecondUntilDestroy ใน ระยะเวลา 1 วินาทีกว่าๆ

ทีนี้ส่วนของ Monster ครับ ตัว Prefabs ทำไว้แล้วมี RigidBody, Character Controller, Capsule ครบหมดแล้วใน Daydev Action Kit ตัวของ Boss ก็เช่นกัน เพียงแค่การทำงานจะต่างกันเล็กน้อย

ตัว Monster ใช้ Code ชื่อ AIEnemy.cs มีหน้าที่คือถ้าเจอ Player วิ่งเข้ามาใกล้ๆ ระยะโจมตีมันจะวิ่งเข้ามาโจมตีทันทีครับ โดยอ้างอิงกับ Animator Controller ที่สร้างไว้ให้แล้ว

using UnityEngine;
using System.Collections;

public class AIEnemy : MonoBehaviour {
	public float Distance = 900f; 
	float MovementSpeed = 3.0f; 
	int Health = 6;
	Animator anim;
	public bool dead = false; 
	public ParticleEmitter AttackEffect;
	public ParticleEmitter Explosion;

	void Start () {
		anim = GetComponent <Animator> ();
	}
	void Update () {

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



	void FixedUpdate () {
		
		// Get the Player object 
		GameObject player = GameObject.Find("Player");
		CharacterController characterController = GetComponent<CharacterController>();
		
		Vector3 AIEyes = transform.position;
		AIEyes.y += characterController.height;
		
		var seeDirection = player.transform.position - AIEyes;
		seeDirection = seeDirection.normalized;
		
		int layerMask = 1 << LayerMask.NameToLayer("Player") | 1 << LayerMask.NameToLayer("Default");
		
		Vector3 AIMovement = Vector3.zero;
		
		RaycastHit hitInfo;
		if (Physics.Raycast(AIEyes, seeDirection,out hitInfo, Distance, layerMask)) {
			if (hitInfo.collider.gameObject == player) {
				AIMovement = seeDirection;
				AIMovement.y = 0;
				AIMovement = AIMovement.normalized;
				anim.SetBool ("AttackPlayer", true);
			}else{
				anim.SetBool ("AttackPlayer", false);
			}
		}
		
		if (AIMovement != Vector3.zero) {
			transform.rotation = Quaternion.LookRotation(AIMovement, Vector3.up);
		}
		characterController.SimpleMove(AIMovement * MovementSpeed); 
	}

	void OnTriggerEnter(Collider theCollision){
		
		if(theCollision.gameObject.name == "Bullet(Clone)"){
			Instantiate(AttackEffect, transform.position, transform.rotation);
			Health = Health-2;
			Debug.Log ("HP: "+Health);
		}
	}
	
	void death(){
		anim.SetBool ("AttackPlayer", false);
		anim.SetBool ("FoundPlayer", false);
		anim.SetTrigger ("Death");
		StartCoroutine (DelayDie());

	}

	IEnumerator DelayDie()
	{
		while (true) {
			yield return new WaitForSeconds(2.0f);
			Instantiate(Explosion, transform.position, transform.rotation);
			Destroy(gameObject);
		}
	}
}

เมื่อมันโดน Bullet(Clone) หรือลูกบอลจาก Player ยิงใส่มันจะลดพลังลง เมื่อตาย จะมี Animator Controller ส่งค่า Trigger ว่า Death แล้วจะค่อยระเบิดหายไปอีกทีประมาณ 2 วินาที

ส่วนตัวของ Boss นั้นจะมี AIBoss.cs คุม จะมีส่วนของการยิงกระสุนเหมือนตัวละคร และจะยิงด้วยความถี่ Cool Down ประมาณ 4 วินาที

using UnityEngine;
using System.Collections;

public class AIBoss : MonoBehaviour {
	public float Distance = 900f; 
	float MovementSpeed = 3.0f; 
	int Health = 100;
	Animator anim;
	public bool dead = false; 
	public ParticleEmitter AttackEffect;
	public ParticleEmitter Explosion;

	public GameObject BossBullet;
	public float fireRate = 1.0f;
	private float nextFire = 0.0f;

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

	void Fire () {
		Instantiate(BossBullet, transform.position, transform.rotation);
	}

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

	public void Winning()
	{
		Debug.Log("Winn!!!!!!");
		isWinner = true;
	}

	
	void FixedUpdate () {
		
		// Get the Player object 
		GameObject player = GameObject.Find("Player");
		CharacterController characterController = GetComponent<CharacterController>();
		
		Vector3 AIEyes = transform.position;
		AIEyes.y += characterController.height;
		
		var seeDirection = player.transform.position - AIEyes;
		seeDirection = seeDirection.normalized;
		
		int layerMask = 1 << LayerMask.NameToLayer("Player") | 1 << LayerMask.NameToLayer("Default");
		
		Vector3 AIMovement = Vector3.zero;
		
		RaycastHit hitInfo;
		if (Physics.Raycast(AIEyes, seeDirection,out hitInfo, Distance, layerMask)) {
			if (hitInfo.collider.gameObject == player) {
				AIMovement = seeDirection;
				AIMovement.y = 0;
				AIMovement = AIMovement.normalized;
				anim.SetBool ("AttackPlayer", true);
				if (Time.time > nextFire) {
					nextFire = Time.time + fireRate;
					Fire ();
				}
				
			}else{
				anim.SetBool ("AttackPlayer", false);
			}
		}
		
		if (AIMovement != Vector3.zero) {
			transform.rotation = Quaternion.LookRotation(AIMovement, Vector3.up);
		}
		characterController.SimpleMove(AIMovement * MovementSpeed); 
	}
	
	void OnTriggerEnter(Collider theCollision){
		
		if(theCollision.gameObject.name == "Bullet(Clone)"){
			Instantiate(AttackEffect, transform.position, transform.rotation);
			Health = Health-2;
			Debug.Log ("HP: "+Health);
		}
	}
	
	void death(){
		anim.SetBool ("AttackPlayer", false);
		anim.SetBool ("FoundPlayer", false);
		anim.SetTrigger ("Death");
		StartCoroutine (DelayDie());
		
	}



	IEnumerator DelayDie()
	{
		while (true) {
			yield return new WaitForSeconds(2.0f);
			Instantiate(Explosion, transform.position, transform.rotation);
			Destroy(gameObject);
			StartCoroutine (WaitForWin());
		}
	}

	IEnumerator WaitForWin()
	{
		while (true) {
			yield return new WaitForSeconds(2.0f);
			Winning();
			isWinner = true;
		}
	}

	void OnGUI(){
		if (isWinner == true) {
			Time.timeScale = 0;
			//Show Total Score
			GUI.Box(new Rect(Screen.width/4, 
			                 Screen.height/4+10, 
			                 Screen.width/2, 
			                 Screen.height/2), 
			        "Win!!!!: ");
			
			//Restart Game
			if (GUI.Button(new Rect(Screen.width/4+10, 
			                        Screen.height/4+Screen.height/10+50, 
			                        Screen.width/2-20, Screen.height/10), 
			               "Next Stage")){
				Application.LoadLevel(Application.loadedLevel);
			}
		}
	}
}

ตัว Monster จะมีการ Random ออกมา ทุกๆ 7 วินาทีเพื่อโจมตีเราดังนั้น เราต้องทำ RandomEnemy ครับ อัดไว้ใน Empty GameObject แล้วทำการโคลนตัว Prefabs ชื่อเดียวกัน

using UnityEngine;
using System.Collections;

public class RandomEnemy : MonoBehaviour {
	public GameObject RandomObjects;
	float Delay_start = 0.0f; 
	float Delay_cooldown = 7.0f;

	void Start () {
		InvokeRepeating("Spawn", Delay_start, Delay_cooldown);
	}

	void Spawn() {
		Instantiate(RandomObjects, transform.position, transform.rotation);
	}
}

Screen Shot 2558-06-06 at 8.27.19 PM

ถ้าให้อธิบาย ชุด Kit ตัวนี้ก็คงมีแค่นี้ครับ เพราะเป็นแค่เครื่องมือแรกๆ ในการทำวิจัยของผม แต่ยังไงก็ยังแจกฟรีให้หลายๆ คนที่อยากจะศึกษานำไปศึกษากันเองต่อได้เองครับ

ตัวอย่าง Video แนะนำการเล่นครับ

ดาวน์โหลดที่นี่: http://bit.ly/DAYDEVKIT

หมายเหตุ: Code ที่ปรากฏ อาจจะมี Pattern ที่มาจาก Document ของ Unity บ้าง หรือในเว็บไซต์บ้าง หากมีคนที่บอกว่า ก็แค่ไปเอา Code มายำๆก็เสร็จ คือมันก็ถูกครับ แค่เชิง Pattern นะครับ แต่ Logic ของเกมผมคิดเองและพัฒนาเองครับ คงไม่ลอกใครมาแน่นอน หากใครจะดราม่าก็รบกวนดราม่าอย่างฉลาดด้วยนะครับ

สำหรับเวอร์ชัน 2: เป็นโครงการในอนาคตครับ หาทุนก่อน ใครอยากบริจาคกี่บาทก็ได้ก็กระซิบมาที่ http://www.facebook.com/daydevthailand เป็นการระดมทุนครับไม่บังคับ จะใช้ฟรีก็ไม่ว่ากันแค่ เครดิตผู้พัฒนาด้วยนะครับ ขอบคุณครับ

Asst. Prof. Banyapon Poolsawas

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

Related Articles

Back to top button

Adblock Detected

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