unity制作小游戏

Posted lesera

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity制作小游戏相关的知识,希望对你有一定的参考价值。

前言

这是专选课3D游戏编程与设计的第二次作业,包括简答题和用unity制作一个小游戏。如有错误,欢迎并感谢指正。

简答题

游戏对象(GameObjects)和资源(Assets)的区别和联系

游戏对象:指Unity中代表人物、道具或场景的基本对象是一个可以容纳各类组件(Component)以实现各类功能的容器。
资源指的是在项目中可能用到的各种资源文件,比如模型、声音文件、贴图文件等等。
区别:游戏对象是游戏运行时存在的对象,而assets时制作游戏时工作区中可以利用的资源。
联系:我们可以用资源创建游戏对象,资源可以是我们实例化具体的游戏对象的模板,也可以作为游戏对象中的某种属性,被多个游戏对象同时使用。

游戏案例分析


资源的目录组织结构:功能类似的资源被放置在同一目录下
游戏对象树的层次结构:游戏对象之间可以为父或子的关系,整体呈树状结构。

编写一个代码,使用 debug 语句来验证 MonoBehaviour 基本行为或事件触发的条件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class intbesh : MonoBehaviour 
 
    private void Awake()
    
        Debug.Log("awake!");
    
 
    void Start () 
        Debug.Log("start!");
	
	
    void Update () 
        Debug.Log("update!");
	
 
    private void FixedUpdate()
    
        Debug.Log("fixedupdate!");
    
 
    private void OnGUI()
    
        Debug.Log("ONGUI!");
    
 
    private void OnDisable()
    
        Debug.Log("OnDisable!");
    
 
    private void OnEnable()
    
        Debug.Log("OnEnable!");
    

控制台因为update输出太多就不展示了,总结如下:

  • Start:第一次进入游戏循环时调用
  • Update:行为启用时,每一帧调用update
  • Awake:当一个脚本实例被载入时
  • FixedUpdate:行为启用时,每一时间片调用
  • OnGUI:渲染和处理GUI事件时调用
  • OnEnable:当对象变为可用或激活状态时被调用
  • OnDisable:当对象变为不可用或非激活状态时调用

查找脚本手册,了解 GameObject,Transform,Component 对象

分别翻译官方对三个对象的描述(Description)

GameObject是Unity中的基本对象,代表人物,道具和游戏场景。它们本身并实现很多东西,但它们可以充当组件的容器,由此实现了真正的功能。
Transform组件决定场景中每个对象的位置,旋转和缩放比例。每个GameObject都有一个Transform。
Component是游戏中对象和行为的细节。它们是每个GameObject的功能部分。

描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件


table对象的属性:name、activeSelf(GameObject的本地活动状态)、isStatic、layer(游戏对象所在的图层。)、tag(游戏对象的标签)

table的Transform的属性有:Position、Rotation、Scale

table的部件有:Mesh Filter、Mesh Renderer、Box Collider、First Beh

资源预设(Prefabs)与 对象克隆 (clone)

预设(Prefabs)有什么好处?

预设可以快速创建具有相同属性的对象,修改预设属性可以同时修改所有对应实例,提高效率

预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?

对象克隆的实例之间不会相互影响,但对预设进行修改会作用到该预设所有的实例上。

制作 table 预设,写一段代码将 table 预制资源实例化成游戏对象


public class FirstBeh : MonoBehaviour 

    public GameObject table;

    void Awake()
    
    	Debug.Log("awake!");
    
    void Start () 
        Debug.Log("Start");
        GameObject aTable = (GameObject)Instantiate(table.gameObject);
        aTable.name = "newTable";
        aTable.transform.position = new Vector3(0,Random.Range(0,5),0);
        aTable.transform.parent = this.transform;
    

    void Update () 
        //Debug.Log("Init Update");
    

编程实践:井字棋小游戏

写一个c#脚本,挂载到摄像机上即可
脚本代码如下:

using UnityEngine;
using System.Collections;

public class TTT : MonoBehaviour 
	//用二位数组存棋盘,0代表空,1代表O,2代表O
	private int[,] chess = new int[3,3] 0,0,0,0,0,0,0,0,0;
	//0是O的回合,1是X的回合
	private int turn = 0;
    private int res=0;
	void Start () 
		
	
    
	//OnGUI会自动刷新
	void OnGUI() 
        //x,y,w,h
		if (GUI.Button(new Rect(310,300,100,50),"重新开始"))  reset();
		
		for (int i = 0; i < 3; i++) 
			for (int j = 0; j < 3; j++) 
                
				if (chess [i,j] == 1) 
					
					GUI.Button (new Rect (70 * i+250, 70 * j, 70, 70), "O");
					Debug.Log(i+" "+j+":"+chess[i,j]);
					res=check();
					Debug.Log(res);
				 else if (chess [i,j] == 2) 
					GUI.Button (new Rect (70 * i+250, 70 * j, 70, 70), "X");
					Debug.Log(i+" "+j+":"+chess[i,j]);
					res=check();
					Debug.Log(res);
				 else 
					if (GUI.Button (new Rect (70 * i+250, 70 * j, 70, 70), "")) 
                        
						if (res == 0) 
							if (turn == 0) 
								chess [i, j] = 1;
								turn = 1;
							 else 
								chess [i, j] = 2;
								turn = 0;
							
						
                    
					
				
                
			
		
        res = check();
		if (res == 1) 
			GUI.Label (new Rect (340, 230, 100, 50), "O赢");
		 else if (res == 2) 
			GUI.Label (new Rect (340, 230, 100, 50), "X赢");
		 else if(res==3)
            GUI.Label (new Rect (340, 230, 100, 50), "平局");
         else
            if(turn==0) GUI.Label (new Rect (335, 230, 100, 50), "O的回合");
            else GUI.Label (new Rect (335, 230, 100, 50), "X的回合");
        
	
	void reset() 
		//重开置空
        turn=0;
        res=0;
		for (int i = 0; i < 3; i++) 
			for (int j = 0; j < 3; j++) 
				chess [i,j] = 0;
			
		
		turn = 0;
	
	int check() 
		//检查行
		for (int i = 0; i < 3; i++) 
			if (chess[i,0]!=0&&chess [i,0] == chess [i,1] && chess [i,1] == chess [i,2]) 
				return chess [i,0];
			
		
		//检查列
		for (int j = 0; j < 3; j++) 
			if (chess[0,j]!=0&&chess [0,j] == chess [1,j] && chess [1,j] == chess [2,j]) 
				return chess [0,j];
			
		
		//检查对角线
		if ((chess[0,0]!=0&&chess [0, 0] == chess [1, 1] && chess [1, 1] == chess [2, 2]) ||
		    (chess[0,2]!=0&&chess [0, 2] == chess [1, 1] && chess [1, 1] == chess [2, 0])) 
			return chess [1, 1];
		
        //检查满
        int count = 0;
        for (int i = 0; i < 3; i++) 
            for (int j = 0; j < 3; j++) 
                if (chess [i, j] != 0)
                    count++;
            
        
        if(count==9)return 3;
		return 0;
	

效果如下


我用Unity制作的第一款游戏Demo

我用Unity制作的第一款游戏Demo

Immortal(不朽)


游戏网址:https://play.unity.com/mg/other/immortal_webgl
或者:点击此处进行游戏

或者:下载游戏 提取码:qv8p

这是我使用 Unity 制作的第一款游戏Demo,一款2D的RPG游戏。
游戏中所有工作均为本人独立完成。

美术方面

哎,早知道该好好学学画画的,对队来说制作这个Demo最耗时、最难的工作就是画画了,画个老半天还是惨不忍睹。

为了不至于不堪入目,果断选择了2D像素风格,游戏中所有的Sprite和Image都是使用Aseprite制作的,比起PS,Aseprite绘制像素画太方便了,爱了爱了。
不知不觉都肝了150多个小时了。。。
角色移动动画

爆炸动画

每个像素都是泪呀T T

技术方面

比较难的点就是对象池的制作,为了保证可扩展性,对象池是用字典存储的,字典通过结构体数组的方式序列化(能在Inspector里直接添加对象简直舒服)。下面是对象池实现的C#代码:

/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour

    //单例模式 
    public static ObjectPool instance;
    //对象类别
    public enum ObjectType
    
        Shadow,
        FireBall,
        FireExplosion,
        Wind
    

    #region 字典的序列化
    [System.Serializable]
    //可序列化的结构体数组(为了序列化字典)
    public struct ObjectDetail
    
        public ObjectType type; //类别
        public GameObject prefab; //预制体
        public int count; //数量
    
    [Header("对象池中的内容")]
    public ObjectDetail[] objectDetails;
    #endregion

    //缓存字典<对象类别, 对象队列>
    public Dictionary<ObjectType, Queue<GameObject>> cache = new Dictionary<ObjectType, Queue<GameObject>>();

    private void Awake()
    
        //单例模式
        instance = this;
        //初始化对象池
        Initialization();
    

    /// <summary>
    /// 初始化对象池并填满
    /// </summary>
    private void Initialization()
    
        foreach (var item in objectDetails)
        
            cache.Add(item.type, new Queue<GameObject>());
            for (int i = 0; i < item.count; i++)
            
                var newObject = Instantiate(item.prefab);
                newObject.transform.SetParent(transform);
                newObject.SetActive(false);
                cache[item.type].Enqueue(newObject);
            
        
    
    /// <summary>
    /// 填满对象池
    /// </summary>
    /// <param name="objectType">对象类别</param>
    public void FillPoll(ObjectType type)
    
        foreach (var item in objectDetails)
        
            if (item.type == type)
            
                for (int i = 0; i < item.count; i++)
                
                    var newObject = Instantiate(item.prefab);
                    newObject.transform.SetParent(transform);
                    newObject.SetActive(false);
                    cache[type].Enqueue(newObject);
                
                return;
            
        
    
    /// <summary>
    /// 返还给对象池
    /// </summary>
    /// <param name="type">对象类别</param>
    /// <param name="gameObject">对象</param>
    public void ReturnPool(ObjectType type, GameObject gameObject)
    
        gameObject.SetActive(false);
        cache[type].Enqueue(gameObject);
    
    /// <summary>
    /// 取出一个对象
    /// </summary>
    /// <param name="type">对象类别</param>
    /// <returns>取出的对象</returns>
    public GameObject GetFromPool(ObjectType type)
    
        if (cache[type].Count == 0)
        
            FillPoll(type);
        
        //出队
        var outObject = cache[type].Dequeue();
        //启用
        outObject.SetActive(true);

        return outObject;
    

技能系统的设计也比较难,为了保证可扩展性,代码改了又改,下面是技能类的实现和火球术的实现。
技能类的实现:

/// <summary>
/// 技能类
/// </summary>
public class Skill

    protected PlayerSkillController playerSkillController; //玩家技能控制器
    public Image CDImage; //CD的UI组件
    //冷却时间
    protected float coolDown;
    //冷却时间计时器
    protected float cdTimer;
    //技能在技能栏中的位置
    private int index;
    //是否可用
    public bool Available  get; set; 

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="coolDown">冷却时间</param>
    /// <param name="index">技能在技能栏中的位置</param>
    protected Skill(float coolDown, int index, PlayerSkillController playerSkillController)
    
        this.coolDown = coolDown;
        cdTimer = coolDown;
        Available = true;
        this.index = index;
        this.playerSkillController = playerSkillController;
    

    /// <summary>
    /// 计算冷却时间
    /// </summary>
    protected void UpdateCD()
    
        if (cdTimer < coolDown)
        
            cdTimer += Time.deltaTime;
        
        else
        
            cdTimer = coolDown;
        
        CDImage.fillAmount = (coolDown - cdTimer) / coolDown;
    

    /// <summary>
    /// 使用技能
    /// </summary>
    /// <returns>使用成功返回true; 失败返回false</returns>
    protected bool Use()
    
        if (cdTimer == coolDown && Available)
        
            cdTimer = 0;
            return true;
        
        return false;
    

火球术的实现:

/// <summary>
/// 火球术
/// </summary>
public class FireBall : Skill, ISkill

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="coolDown">冷却时间</param>
    /// <param name="index">技能在技能栏中的位置</param>
    /// <param name="playerSkillController">玩家技能控制器</param>
    public FireBall(float coolDown, int index, PlayerSkillController playerSkillController) : base(coolDown, index, playerSkillController)
    
        CDImage = GameObject.FindWithTag("FireBallCD").GetComponent<Image>();
    

    public void Update()
    
        UpdateCD();
    

    public new bool Use()
    
        if (base.Use())
        
            cdTimer = 0;
            GameObject fireBall = ObjectPool.instance.GetFromPool(ObjectPool.ObjectType.FireBall);
            fireBall.GetComponent<FireBallController>().SetProperty(
                playerSkillController.fireBallActiveTime,
                playerSkillController.fireBallDamage,
                playerSkillController.fireBallSpeed,
                playerSkillController.skillDirection,
                playerSkillController.transform.position
                );
            return true;
        
        return false;
    

游戏内容


游戏目前实现了人物的移动和三种不同类型的技能,怪物的移动和两种不同类型的技能,人物与怪物的受伤提示(颜色变化)和死亡效果,告示牌的互动对话UI系统,游戏的菜单UI系统,游戏的暂停、重开和退出功能,小地图功能等。

技能介绍:
火球术:通过对象池实现。使用了拖尾效果和粒子系统,包括飞行时间限制、碰撞检测、击中碰撞器发生爆炸的效果。

疾步:通过对象池实现人物残影效果,移速增加Buff。

掌心雷:通过Trigger实现检测怪物与三段伤害,通过RaycastHit2D实现遮挡识别。

怪物技能:
旋风:通过对象池实现,通过旋转矩阵实现8个方向同时发射。

风阵:通过Trigger实现检测玩家与持续伤害。

怪物实现:
通过仇恨距离与RaycastHit2D实现怪物仇恨系统与追随玩家功能。

通过随机方式选择怪物技能。

部分C#脚本:

推荐教程:
B站UP主: M_Studio
YouTuber:CouchFerret makes GamesB站搬运

以上是关于unity制作小游戏的主要内容,如果未能解决你的问题,请参考以下文章

unity3D如何设计一个游戏角色选择界面?

单体游戏按钮

基于UNITY引擎开发的游戏源码修改方法

求教如何在Unity中为2D游戏制作技能特效

我们可以用团结的python制作游戏吗? [关闭]

Scanvenger游戏制作笔记Unity3D控制游戏的失败