unity3d 简单游戏_unity3d简单游戏教程 - CSDN
  • 实现角色动画和移动,场景管理和场景切换,按钮事件,以及对象碰撞等内容,适合刚刚学习unity3D的人
  • Unity3D游戏引擎最详尽基础教程

    万次阅读 2018-06-27 15:23:06
    我一直向所有想做游戏的朋友推荐Unity3D,为什么呢?首先是因为专业,Unity3D非常强大,用它创建一个类似MiniGore这样的3D平面射击游戏简直轻而易举,而就连使命召唤这样的大型3d游戏,如果素材得当也不在话下。二是...

    我一直向所有想做游戏的朋友推荐Unity3D,为什么呢?首先是因为专业,Unity3D非常强大,用它创建一个类似MiniGore这样的3D平面射击游戏简直轻而易举,而就连使命召唤这样的大型3d游戏,如果素材得当也不在话下。二是因为易用,他的操作非常容易,而使用的脚本语言又是JavaScript或者C#,不仅非常简单,也让各个种类的程序员都非常容易上手。再次,就是因为免费,如果你不使用Pro或者Mobile的内容的话,Unity3d就是完全免费的。还有就是对3D模型的兼容性,几乎任何3d模型都可以导入到unity中,可以说是一个很方便的特性。

     

    今天,我就来讲讲Unity3D的基础操作,这可以说是一个新手必读吧,读完之后,你一定会惊叹,原来做一个游戏这么简单。

     

    第一节 加入重力

     

    我们先打开Unity3d,新建一个场景(Scene),新建的时候应该会有对话框要求你加入哪些Asset Package,记得选择Physics Material,因为后面我们一定要用的哦亲。

     

    如果创建的时候没加,你可以再后来的Asset菜单里加:

     

     

    之后,我们建立一个Cude,调整一下x和z的大小,我这里调成了25,让它变成一个地板,你也可以放一个texture上去,看起来更加真实,我这里从简,再创建一个sphere,在component中选择Physics中的RigidBody,前提是选中你的sphere物体,之后会看到这个Sphere的inpector中加入一个component叫做RigidBody,调整好Camera运行一下,你就会发现Sphere受到重力的影响自动掉落到地板上。

     

     

    之后,通过我们之前引入的Physics Materials资源包,你还可以选择这个物体的物理性质,我这里选择Bouncy(跳跳),落下之后,这个物体就会蹦来蹦去的,呵呵

     

     

     

    第二节 检测碰撞

     

    下面我们通过简单的JavaScript语句来检测碰撞,这在制作游戏中是很有用的,比如说检测到子弹和敌人的碰撞之后,可以使用我们后来要讲到destory命令消灭敌人。

     

    这次我们新建一个Terrain项目,在用Grass贴图来覆盖住它,如果你找不到贴图的话,记得在Asset菜单里找找,看看哪些资源你还没有引入进去。

     

    然后再加入一个Cube项目,我这里叫做PlayerCube,为他加上重力,之后选择bouncy性质,然后我们再新建一个Cude,调整y和z的值让他变成一堵墙,放置好,命名为Wall,别忘了给Terrain改名为Terrain,

     

    之后我们在Asset菜单中建立一个JavaScript项目,改名为CollisionDetect,双击打开,如果你使用的是Unity 3.5的话,就会打开MonoDev,这是一个新工具还是很好用的,在其中新建一个函数,别管Start和Update函数,我们待会会讲。

     

    加入以下代码:

     

    function OnCollisionEnter(theCollision : Collision){
    
     
    
    if(theCollision.gameObject.name=="Terrain"){
    
     
    
    Debug.Log("Hit terrain");
    
     
    
    }else if(theCollision.gameObject.name=="Wall"){
    
     
    
    Debug.Log("Hit the wall");
    
     
    
    }
    
     
    
    }
    

     

    如果看不懂这些代码的话,也没关系,JavaScript是最好学的编程语言,Google一下吧,保存这个JS文件,然后拖到我们的PlayerCube上。

     

    这时候我们运行,就会看到PlayerCube到处蹦,碰到Terrain的时候,控制台就会显示Hit Terrain,碰到wall的时候就会显示Hit the wall。

     

    第三节  检测用户输入

     

    下面我们来讲一下如何检测用户的键盘和鼠标的操作。

     

    就用我们上次的那个场景,这次我们给PlayerCube加上我们可以控制的动作。就是通过“wasd”或者上下左右键来控制移动。

     

    请注意,如果要使用这里使用的方法来控制移动,那么所控制的物体不能够是一个物理世界的物体,也就是说你要把RigidBody这个component删掉,如果你想要在物理世界里移动物体的话,你就需要我们在下面会讲到的力(force)。

     

    首先我们新建一个JavaScript文件,这里命名为InputDetect吧,输入下面的代码:

     

    #pragma strict
    
     
    
    var Player : GameObject;
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    Player = GameObject.Find("PlayerCube");
    
     
    
    if(Input.GetKey(KeyCode.W)){
    
     
    
    Debug.Log("Hit w");
    
     
    
    Player.transform.Translate(Vector3.forward * 2);
    
     
    
    }else if(Input.GetKey(KeyCode.S)){
    
     
    
    Debug.Log("Hit s");
    
     
    
    Player.transform.Translate(Vector3.back * 2);
    
     
    
    }else if(Input.GetKey(KeyCode.A)){
    
     
    
    Debug.Log("Hit a");
    
     
    
    Player.transform.Translate(Vector3.left * 2);
    
     
    
    }else if(Input.GetKey(KeyCode.D)){
    
     
    
    Debug.Log("Hit d");
    
     
    
    Player.transform.Translate(Vector3.right * 2);
    
     
    
    }
    
     
    
     
    
    }
    

     

    这时,我们运行这个游戏,就可以通过“WASD”来控制PlayerCube的移动了。

     

     

    第四节 使用Prefab复制和控制物体

     

    我们可以直接把Prefab看做是个妈,她能生好多小孩,如果妈妈的DNA变了,小孩的跟着变,就是说你可以用Prefab创建物体,然后通过这个Prefab修改所有这类物体的性质,这对于批量生成的敌人或者NPC来说很有用。

     

    首先,先创建一个Prefab,我这里命名为“Mother”,之后新建一个Sphere物体,当然你也可以建一些其他的物体,之后给这个Sphere加上你想要的性质,我加入了RigidBody和Bouncy,之后将Sphere拖到Mother上,你会发现mother变蓝了,之后你就可以随意拖一些mother物体到屏幕上去,你会发现所有的Prefab创建出的物体在清单上都是蓝色的,说明他们都有共同的性质。

     

     

    当然,作用于一个Prefab上的脚本也会作用到它的子物体上去。

     

    第五节 使用Destroy命令消灭物体

     

    我们游戏中怎么能够没有敌人呢?有敌人就得能够消灭他们,这时候,我们就会使用Destroy命令了。

     

    在这里,我们使用上次Mother生出来的小球球作为敌银,一旦我们的PlayerCube碰撞到了小球的话,就是用Destroy让小球消失。

     

    新建一个JavaScript脚本,输入以下代码:

     

    #pragma strict
    
     
    
    var mother : GameObject;
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    }
    
     
    
    function OnCollisionEnter(theCollision : Collision){
    
     
    
    mother = GameObject.Find("Mother");
    
     
    
    if(theCollision.gameObject.name=="Mother"){
    
     
    
    Debug.Log("Hit mother");
    
     
    
    Destroy(mother);
    
     
    
    }
    
     
    
    }
    

     

    这段代码很好懂吧,就是检验碰撞,然后销毁碰撞到的Mother物体,别忘了把Destroy的JavaScript文件拖到PlayerCube上去。运行一下,就可以看到碰撞到的物体都被消灭了。

     

     

    第六节 使用Instantiate命令创造物体

     

    我们直接在界面创造的物体都只是会在界面被初始化的时候加载,那么,如果为了节约内存或者其他什么事情,我想要在游戏过程中添加物体,那该怎么办呢?你猜对了,就是用instantiate命令。

     

    这个命令就是英文实例化的意思,下面我们给碰撞之后消失的mother物体继续初始化一些,也就是说消灭了一些,又来了一大波···

     

    为什么非要用mother呢?因为只有拥有Prefab的物体才能够使用这个命令哦亲。

     

    function Update () {
    
     
    
    var instance : GameObject = Instantiate (PreFab,transform.position,transform.rotation);
    
     
    
    }
    

     

    注意:这样的话,每次刷新一帧都会出现一个新的mother物体,你的内存也许会受不了哦,更合理的方法是按下空格键,然后出现一个新的物体,你可以自己做吗?试试看

     

    你可能已经注意到了,每一个初始化的脚本都会默认有两个函数,就是:

    Update()函数 —— 每刷新一帧都会检查并执行

    Start()函数 —— 只在场景初始化的时候执行

     

    第七节 使用Timer计时器

     

    这次,我们要让一个计时器倒数,倒数到0的时候创建一个mother物体,来看Javascript代码吧:

     

    #pragma strict
    
     
    
    var myTimer : float = 5.0;
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    if (myTimer >= 0){
    
    myTimer -= Time.deltaTime;
    
    guiText.text=(""+myTimer);
    
    }
    
     
    
    if (myTimer <= 0){
    
    guiText.text = ("Done");
    
    }
    
     
    
    }
    

     

    把这个脚本拖到GUItext控件上,你会发现我们所期待的效果。

     

     

    第八节 让物体动起来

     

    其实让物体行动的技巧,我们已经会了,不是吗?

     

    如果你觉得不是的话,没有认真的看上面的文章哦,我就再给你讲一次吧。

     

    请特别注意,这种方法不适合物理世界的物体,如果向移动物理世界的物体,请看下一节:

     

    创建一个Cylinder物体,之后将下面的脚本附着在上面。

     

    #pragma strict
    
     
    
    var myObject : GameObject;
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    myObject.gameObject.Find("Cylinder");
    
    myObject.transform.Translate(Vector3(0,0,1));
    
     
    
    }
    

     

    第九节 使用物理世界的力

     

    这里是我们讲移动物体的重点,因为大部分的物体移动都是在物理世界完成的,而如果想要在物理世界移动东西,除了position和rotation的移动之外,就是使用“力”了,在高中学过经典物理学的人都知道,要使物体用移动,木有力是不行的,那么我们就开始吧,再新建一个什么物体,我新建了一个Capsule。

     

    之后,我会在这个物体的上方加一个力,让它用力的向下飞,触碰到地之后,通过bouncy这个性质再弹回来。新建一个JavaScript文件,就叫做Force吧,加入如下代码:

     

    #pragma strict
    
     
    
    var Power : float = 500.0;
    
     
    
    function Start () {
    
     
    
    rigidbody.AddForce(Vector3(0,Power,0));
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    }
    

     

    运行之后,会发现capsule其实是个很有幽默感的物体。

     

     

    第十节 使用贴图和材质

     

    贴图和材质能够使你的虚拟3D世界看起来更加真实,不幸的是,需要大量在PS和AI中的设计工作,最近我一想到要打开PS就有点烦···呜呜。

     

    闲话少说,我已经做好了一个贴图,怎么使用在材质上呢?之后我们怎么才能通过材质使用到其他物体之上呢?

     

     

    就像你看到的一样,我是直接拖进去的···

     

    之后我们从新建菜单中选择Material这一项,之后选中material,将贴图拖进material的贴图选框里,之后,就可以拖动这个Material赋给任何物体了。

     

     

    第十一节 使用声效和音乐

     

    音效和音乐对于一个游戏来说是非常重要的,一个寂静无声的世界是无聊的,而且不真实,什么?你说声效和音乐是一个东西?那你就错了,声效是,比如说发射子弹的声音,什么东西爆炸的声音等等,而音乐则是背景音乐什么的,不一样的亲。

     

    对于音乐来说,往往使用立体声音乐,而对于生效来说,我们随便使用一个简单的音乐文件就可以了。

     

    想要在Unity3D中播放音乐,你需要两样东西,一个是监听器,一个是音乐源,监听器是Camera的一个组件,所以你不用担心,你唯一需要增加的就是音乐源。

     

    直接将项目中的音乐拖放到Audio Source项就可以播放,但是如果我们想要在某个特定的点上 播放音乐该怎么办呢?答案就是,写一个脚本,新建一个脚本,输入如下代码:

     

    #pragma strict
    
     
    
    var myClip : AudioClip;
    
     
    
    function Start () {
    
     
    
    AudioSource.PlayClipAtPoint(myClip,transform.position);
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    }
    

     

    之后将这个脚本拖放到我们的物体上,最后,将音频文件拖放到AudioSource选项框中。

     

    第十二节 使用关节

     

    关节在3D游戏之中的应用非常广,主要在于物理世界的重力影响,下面我们就通过关节来制作一个钟摆。

     

    这次我们新建一个场景,松了一口气?刚才的场景是不是太乱了?你做的时候可以整理一下嘛,呵呵

     

    新建一个cube作为地面,之后一个cube放在上空作为摆垂,之后用几个capsule分别用component中的fixed joint连接起来,之后最上面的那个capsule与世界相连,使用Hinge Joint,这样的话,他会默认与世界相连,不用担心,看图

     

     

     

    第十三节 使用Axes

     

    在Edit – Project Setting – input 中可以查看各种形式的input,这些input方法在unity中被叫做Axes。

     

    我们创建一个JavaScript,叫做GetAxes,输入以下内容:

     

    #pragma strict
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    var Honriz : float = Input.GetAxis("Honrizontal");
    
     
    
    Debug.Log(Honriz);
    
     
    
    }
    

     

    在拖放脚本之后,运行这个场景,看看控制台输出了什么,你就明白了。

     

     

    第十四节 使用触发器

     

    触发器一般用于检测一个物体是否处于一个空间内,他是怎么做到的呢?

     

    首先,他创造一个大的物体,之后将他的randerer属性去掉,他就隐形了,这样,我们再把他的isCollide属性去掉,他就不会发生碰撞了,之后,我们只需要用脚本检测物体是否与这个物体碰撞就可以了。

     

    我们新建一个场景,做一个地板,再做一个底座,叠加起来,再在他们的上空做一个方块,设置rigidbody属性,将底座设置成为我们刚才说的那种隐形的状态,之后新建一个JavaScript脚本,输入代码

     

    #pragma strict
    
     
    
    function Start () {
    
     
    
    }
    
     
    
    function Update () {
    
     
    
    }
    
     
    
    function OnTriggerEnter (myTrigger : Collider) {
    
     
    
    if(myTrigger.gameObject.name == "box"){
    
    Debug.Log("Hit");
    
    }
    
     
    
    }
    

     

    按下运行,会发现可以检测出Hit。

     

     

    第十五节 使用GUI

     

    其实我们已经学过了如何使用GUI text,不信,我们就用屏幕上的字代替我们在上面的程序中的Debug.Log现实的内容。

     

    你自己能做吗?

     

    试试看吧,一个下午写了这么多,有点累。

     

    一定要在Unity中加入一个GUI Text,运行一下,就会发现屏幕上的GUI Text会随着你设定的的内容而变化了。

    失败是什么?没有什么,只是更走近成功一步;成功是什么?就是走过了所有通向失败的路,只剩下一条路,那就是成功的路。
    展开全文
  • 5款Unity3D制作的小游戏实例,新手必备
  • 基本程序设计(故事卡) 游戏会为玩家呈现一个“故事卡”。故事卡上包含一些文字,其中一部分是用于描述玩家当前的...制作一张“故事卡”很简单。根据上诉需求,我们新建StoryCard脚本,脚本代码如下: StoryCard在...

    一、前言

    游戏灵感来自于“火柴人亨利(Henry Stickmin)”系列游戏,以及一些上世纪80年代的《惊险岔路口》冒险丛书。游戏根据玩家的不同选择来展开故事情节,通常会拥有多重结局。

    效果图
    在这里插入图片描述

    二、示例工程下载

    https://download.csdn.net/download/q764424567/12472942

    三、程序设计

    3-1、基本程序设计(故事卡)

    游戏会为玩家呈现一个“故事卡”。故事卡上包含一些文字,其中一部分是用于描述玩家当前的状态,另外一部分是在当前情况下玩家可以做出的一系列选择。

    在这里插入图片描述
    根据玩家的不同选择,剧情也会按照不同的分支向前发展,并持续出现新的卡片与选择,直到最终的卡片不再有新的选择,则游戏结束。

    制作一张“故事卡”很简单。根据上诉需求,我们新建StoryItemBase脚本,脚本代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class StoryItemBase : MonoBehaviour
    {
        // Use this for initialization
        void Start()
        {
        }
    
        // Update is called once per frame
        void Update()
        {
        }
    
        public virtual void Activate(GameManager gm)
        {
        }
    }
    

    我们新建StoryCard脚本,脚本代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class StoryCard : StoryItemBase
    {
        public string Description;
    
        public string[] Options;
        public StoryItemBase[] Items;
    
        public StoryState[] StatesToSetTrue;
        public StoryState[] StatesToSetFalse;
    
        public override void Activate(GameManager gm)
        {
            gm.SetCardDetails(Description, Options, Items);
    
            UpdateStates();
        }
    
        private void UpdateStates()
        {
            if (StatesToSetTrue != null)
            {
                foreach (StoryState s in StatesToSetTrue)
                    s.Value = true;
            }
    
            if (StatesToSetFalse != null)
            {
                foreach (StoryState s in StatesToSetFalse)
                    s.Value = false;
            }
        }
    }
    
    

    StoryCard在检视面板中显示如下:
    在这里插入图片描述

    您需要在StoryCard脚本:
    Description:输入卡片描述(玩家可以在屏幕上看到的文本)
    Options:玩家在选项按钮上看到的文字(目前该程序最多支持4个按钮)
    Items:以及按下对应按钮后跳转的故事卡(即为分支,稍后会介绍)

    StoryCard脚本还包含两个故事状态属性“States to Set True”以及“States to Set False”,这两个属性分别有何作用呢?下面就来看看。

    3-2、故事状态与分支

    其实整个系统可以完全使用“故事卡”来制作,但仅使用“故事卡”的话,游戏流程就变得很枯燥无味。
    假设有某个选项,玩家点击了该按钮,但该选项所导致的后果并不会立即在剧情中呈现,而是在随后的剧情中缓慢展开。
    假使仅使用“故事卡”,就需要立即从该选项开始出现分支,并且直至该选项导致的最终影响出现之前,都要在所有剧情分支上重复同样的步骤。

    因此,我们需要一个更好的解决办法。

    前面提出了“故事状态”的概念用于存储状态值,这其实只是一个布尔值容器。
    卡片在被激活时可以根据需要对特定的状态进行赋值。新建StoryState脚本,代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class StoryState : MonoBehaviour 
    {
      public bool Value;
    }
    
    

    StoryState脚本在检视面板中显示如下图:
    在这里插入图片描述

    该方法解决了记住状态值的问题,下面通过“剧情分支(Story Branch)”来应用这些状态值。
    一个分支会引用一个状态以及剧情发展的两个不同方向(可以是“故事卡”或者剧情分支),被引用的状态值用于决定剧情走向何种结果。

    3-3、游戏管理器

    这个管理器用于承载整个剧情的发展,其作用是将目前的“故事卡”状态更新到UI上,在不同按钮按下时做出对应的动作,并引导剧情前进。
    这个管理器也需要一个“故事卡”来作为故事的开端。以下是GameManager脚本的内容:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using System;
    
    public class GameManager : MonoBehaviour
    {
        public Text Description;
        public Button[] OptionButtons;
    
        public StoryItemBase CurrentItem;
    
        private int _numButtons;
        private Text[] _buttonTexts;
        private string[] _optionTexts;
        private StoryItemBase[] _optionItems;
    
        // Use this for initialization
        void Start()
        {
            _numButtons = OptionButtons.GetLength(0);
    
            GetButtonTexts();
    
            CurrentItem.Activate(this);
        }
    
        // Update is called once per frame
        void Update()
        {
    
        }
    
        private void GetButtonTexts()
        {
            _buttonTexts = new Text[_numButtons];
    
            for (int i = 0; i < _numButtons; i++)
            {
                _buttonTexts[i] =
                  OptionButtons[i].GetComponentInChildren<Text>(true);
            }
        }
    
        public void SetCurrentStoryItem(StoryItemBase item)
        {
            CurrentItem = item;
            CurrentItem.Activate(this);
        }
    
        public void OnButton(int index)
        {
            SetCurrentStoryItem(_optionItems[index]);
        }
    
        public void SetCardDetails(string desc, string[] optionTexts,
                                   StoryItemBase[] optionItems)
        {
            Description.text = desc;
            _optionTexts = optionTexts;
            _optionItems = optionItems;
    
            UpdateButtons();
        }
    
        public void UpdateButtons()
        {
            int numOptionTexts = _optionTexts == null ? 0 :
                                                   _optionTexts.GetLength(0);
            int numOptionItems = _optionItems == null ? 0 :
                                                   _optionItems.GetLength(0);
    
            int numActiveButtons = Math.Min(numOptionItems, numOptionTexts);
    
            for (int i = 0; i < _numButtons; i++)
            {
                if (i < numActiveButtons)
                {
                    OptionButtons[i].gameObject.SetActive(true);
                    _buttonTexts[i].text = _optionTexts[i];
                }
                else
                {
                    OptionButtons[i].gameObject.SetActive(false);
                }
            }
        }
    
    }
    
    

    其中SetCurrentStoryItem函数用于设置当前显示的剧情节点。
    SetCardDetails函数用于设置“故事卡”的细节,例如剧情描述,选项按钮及描述等。

    UpdateButtons函数用于更新所有选项按钮及其响应事件。

    GameManager脚本在检视面板中显示如下图:
    在这里插入图片描述

    展开全文
  • 这个游戏属于比简单的,大神可以直接无视,如果有做错的地方请大家多多指点,我也是刚学如何做游戏。代码是用C#编写,主要实现的功能有三个:第一人称移动控制、角色控制(如射击)、TCP服务端和客户端。

    简介:

    这一篇文章主要是和大家分享如何制作一个属于自己的“第一人称射击游戏”,而且是要可以多人联机的。这个游戏属于比简单的,大神可以直接无视,如果有做错的地方请大家多多指点,我也是刚学如何做游戏。代码是用C#编写,主要实现的功能有三个:第一人称移动控制、角色控制(如射击)、TCP服务端和客户端。我将项目命名为FPS。


    游戏运作流程:

    服务端

    1. 创建服务端

    2. 从各个客户端接收数据,然后广播给所有客户


    客户端

    1. 连接至服务端

    2. 从服务端接收到其他客户的数据(比如当前位置,是否有开枪等等),然后更新到游戏上

    3. 发送自己当前在游戏的状态给服务端


    联机效果图:

    服务端



    客户端



    完整源代码与已编译好的游戏程序:

    下载


    C#脚本简介:

    1. FirstPersonControl.cs

    这个脚本是用来做第一人称控制的

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class FirstPersonControl : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
            // 获取摄像头对象
            mCamera = transform.Find("Main Camera");
            // 获取右手对象
            mRightHand = transform.Find("RightHand");
            // 获取脚步声播放组建
            mAudio = transform.GetComponent<AudioSource>();
    	}
    
        // Update is called once per frame
        void Update() {
            if (UpdateMovement()) {
                PlayStepSound();
            } else {
                StopPlayStepSound();
            }
    
            UpdateLookAt();
        }
    
        // 第一人称控制器的常量与变量
        private Transform mCamera;
        private Transform mRightHand;
        private AudioSource mAudio;
        public float mMoveSpeed = 8;         // 物体移动速度
        public float mMouseSensitivity = 5;     // 鼠标旋转的敏感度
        public float mMinimumX = 325;       // 向下望的最大角度
        public float mMaximumX = 45;        // 向上望的最大角度
        public float mMinimumY = -360;         // 向左望的最大角度
        public float mMaximumY = 360;       // 向右望的最大角度
        private Vector3 _curRotation = new Vector3(0,0,0);       // 当前旋转角度
    
        // 更新移动位置
        private bool UpdateMovement() {
            float distance = mMoveSpeed * Time.deltaTime;   // 移动距离
            
            // 前
            if (Input.GetKey(KeyCode.W)) {
                float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                transform.Translate(new Vector3(x,0,z), Space.World);
                return true;
            }
    
            // 后
            if (Input.GetKey(KeyCode.S)) {
                float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                transform.Translate(new Vector3(-x, 0, -z), Space.World);
                return true;
            }
    
            // 左
            if (Input.GetKey(KeyCode.A)) {
                transform.Translate(new Vector3(-distance, 0, 0));
                return true;
            }
    
            // 右
            if (Input.GetKey(KeyCode.D)) {
                transform.Translate(new Vector3(distance, 0, 0));
                return true;
            }
    
            return false;
        }
    
        // 更新摄像头指向位置
        private void UpdateLookAt() {
            // 左右旋转
            _curRotation.y = _curRotation.y + Input.GetAxis("Mouse X") * mMouseSensitivity;
            _curRotation.y = Mathf.Clamp(_curRotation.y, mMinimumY, mMaximumY);
    
            // 设置身体
            Vector3 rotation = transform.eulerAngles;
            rotation.y = _curRotation.y;
            transform.eulerAngles = rotation;
    
            // 上下旋转
            _curRotation.x = _curRotation.x  - Input.GetAxis("Mouse Y") * mMouseSensitivity;
            _curRotation.x = Mathf.Clamp(_curRotation.x, mMinimumX, mMaximumX);
    
            // 设置摄像头
            mCamera.localEulerAngles = new Vector3(_curRotation.x, 0, 0);
    
            // 设置右手
            rotation = mRightHand.eulerAngles;
            rotation.x = _curRotation.x;
            mRightHand.eulerAngles = rotation;
        }
    
        // 播放脚步声
        private void PlayStepSound() {
            if (!mAudio.isPlaying) {
                mAudio.Play();
            }
        }
    
        // 停止播放声音
        private void StopPlayStepSound() {
            if (mAudio.isPlaying) {
                mAudio.Stop();
            }
        }
    }
    


    2. Character.cs

    角色控制脚本,主要是用来控制开枪等角色动作

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Character : MonoBehaviour {
    
        // Use this for initialization
        void Start() {
            // 获取摄像头对象
            mCamera = transform.Find("Main Camera");
            // 获取右手对象
            mRightHand = transform.Find("RightHand");
            // 获取枪声播放组件
            mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
            // 获取火花效果
            mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
            // 获取网络组件
            mNetwork = transform.GetComponent<Network>();
        }
    
        // Update is called once per frame
        void Update() {
            UpdateFire();
    
            // 发送当前状态到服务端,然后服务端就会转发给其他客户
            mNetwork.SendStatus(transform.position, transform.eulerAngles, 
            mCamera.eulerAngles, mRightHand.eulerAngles, _isShooted, _hp);
    
            // 处理服务器发过来的数据包,数据包里装着其他客户的信息
            ProcessPackage();
        }
    
        private Transform mCamera;
        private Transform mRightHand;
        private AudioSource mGunAudio;
        public GameObject mPiece;       // 开枪后撞击产生的碎片
        private ParticleSystem mFireEffect;     // 开枪后的火花
        private bool _isShooted;    // 判断是否开了枪
        private Network mNetwork;       // 网络组件
        public GameObject mEnemyCharacter;  // 其他客户的实例
        private Hashtable _htEnemies = new Hashtable();    // 其他客户的控制脚本
    
        // 开枪
        private void UpdateFire() {
            if (Input.GetButtonUp("Fire1")) {
                // 射击音效与画面
                PlayShotSound();
    
                // 播放火花效果
                PlayFireEffect();
    
                // 判断射击位置
                RaycastHit hit;
                if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
                    // 被枪击中的地方会有碎片弹出
                    DrawPieces(hit);
                }
    
                // 设置开枪判断
                _isShooted = true;
            } else {
                // 设置开枪判断
                _isShooted = false;
            }
        }
    
        // 播放枪声
        private void PlayShotSound() {
            mGunAudio.PlayOneShot(mGunAudio.clip);
        }
    
        // 画碎片
        private void DrawPieces(RaycastHit hit) {
            for (int i = 0; i < 5; ++i) {
                GameObject p = Transform.Instantiate(mPiece);
    
                // 碎片撞击到物体后的反弹位置
                Vector3 fwd = mCamera.forward * -1;
                p.transform.position = hit.point;
                p.GetComponent<Rigidbody>().AddForce(fwd * 100);
    
                // 0.3秒后删除
                Destroy(p, 0.3f);
            }
        }
    
        // 播放火花效果
        private void PlayFireEffect() {
            mFireEffect.Play();
        }
    
        // 人物变量
        private int _hp = 100;
    
        // 受到伤害
        public void GetHurt() {
            _hp -= 10;
    
            if (_hp <= 0) {
                // 复活
                Revive();
            }
        }
    
        // 复活
        private void Revive() {
            _hp = 100;
            transform.position = new Vector3(0,1,0);
        }
    
        // 处理数据包
        private void ProcessPackage() {
            Network.Package p;
    
            // 获取数据包直到完毕
            while (mNetwork.NextPackage(out p)) {
                // 确定不是本机,避免重复
                if (mNetwork._id == p.id) {
                    return;
                }
    
                // 获取该客户相对应的人物模组
                if (!_htEnemies.Contains(p.id)) {
                    AddEnemyCharacter(p.id);
                }
    
                // 更新客户的人物模型状态
                EnemyCharacter ec = (EnemyCharacter)_htEnemies[p.id];
    
                // 血量
                ec.SetHP(p.hp);
    
                // 移动动作
                ec.Move(p.pos.V3, p.rot.V3, p.cameraRot.V3, p.rightHandRot.V3);
    
                // 开枪
                if (p.isShooted) {
                    ec.Fire();
                }
            }
        }
    
        // 增加客户的人物模组
        private EnemyCharacter AddEnemyCharacter(int id) {
            GameObject p = GameObject.Instantiate(mEnemyCharacter);
            EnemyCharacter ec = p.GetComponent<EnemyCharacter>();
            
            // 修改ID
            ec.SetID(id);
    
            // 加入到哈希表
            _htEnemies.Add(id, ec);
    
            return ec;
        }
    
        // 删除客户的人物模组
        private void RemoveEnemyCharacter(int id) {
            EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
            ec.Destroy();
            _htEnemies.Remove(id);
        }
    
        // 删除所有客户的人物模组
        public void RemoveAllEnemyCharacter() {
            foreach (int id in _htEnemies.Keys) {
                EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
                ec.Destroy();
            }
            _htEnemies.Clear();
        }
    }
    


    3.Network.cs

    网络组件,用来创建服务端或客户端。服务端负责广播从各个客户端接收的数据给所有客户。客户端则是接收从服务端接收其他客户的数据。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Net;
    using System.Net.Sockets;
    
    public class Network : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
            // 获取人物组件
            mCharacter = transform.GetComponent<Character>();
            // 数据包大小
            _packageSize = PackageSize(); 
    	}
    	
    	// Update is called once per frame
    	void Update () {
    
    	}
    
        // 网络设置UI
        void OnGUI() {
    		GUI.Box (new Rect(0,0,800,60),"网络设置");
    
            if (_connected) {
    		    GUI.Label (new Rect(10,25,100,25), _isServer ? "已建立服务端":"已连接服务端");
            } else if (!_connected) {
    		    GUI.Label (new Rect(10,25,100,25), "未连接");
            }
    
    		GUI.Label (new Rect(130,25,20,25), "IP:");
            GUI.Label (new Rect(270,25,40,25), "端口:");
            GUI.Label (new Rect(380,25,40,25), "ID:");
    
    		if (!_connected && !_isServer) {
    			_ip = GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
                _port = System.Convert.ToInt32 (GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100));
                _id = System.Convert.ToInt32 (GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100));
    		} else {
    			GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
                GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100);
                GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100);
            }
    
            if (!_connected && !_isServer) {
                if (GUI.Button(new Rect(540,25,100,25), "开启服务端")) {
                    StartServer();
                    ConnectServer();
                }
    
                if (GUI.Button(new Rect(660,25,100,25), "连接至服务端")) {
                    ConnectServer();
                }
            } else {
                if (_isServer) {
                    if (GUI.Button(new Rect(540,25,100,25), "关闭服务端")) {
                        StopServer();
                    }
                } else if (_connected) {
                    if (GUI.Button(new Rect(540,25,100,25), "取消连接")) {
                        DisconnectServer();
                    }
                }
            }
    	}
    
        // 服务端和客户端的共有变量
        private Character mCharacter; // 人物组件
        private bool _connected=false;  // 判断是否已经建立连接或开启服务端
        private bool _isServer=false;   // 判断本程序建立的是服务端还是客户端
        private string _ip = "127.0.0.1";           // 主机IP
        private int _port = 18000;             // 端口
        public int _id = 1 ;           // 人物id
        List<Package> _packages=new List<Package>();        // 数据包
        private int _packageSize;        // 数据包大小
    
        // 数据包
        [Serializable]
        public struct Package {
            public int id;
            public Vector3Serializer pos;   // 人物位置
            public Vector3Serializer rot;   // 人物旋转角度
            public Vector3Serializer cameraRot;     // 摄像头旋转角度
            public Vector3Serializer rightHandRot;  // 右手旋转角度
            public bool isShooted;      // 判断是否有开枪
            public int hp;      // 血量
        }
    
        // 获取包大小
        private int PackageSize() {
            Package p = new Package();
            byte[] b;
            Serialize(p, out b);
            return b.Length;
        }
    
        // 可序列化的Vector3
        [Serializable]
    	public struct Vector3Serializer {
    		public float x;
    		public float y;
    		public float z;
    
    		public void Fill(Vector3 v3) {
    			x = v3.x;
    			y = v3.y;
    			z = v3.z;
    		}
    
    		public Vector3 V3
    		{ get { return new Vector3(x, y, z); } }
    	}
    
        // 序列化数据包
        public bool Serialize(object obj, out byte[] result) {
            bool ret = false;
            result = null;
    
            try {
                MemoryStream ms = new MemoryStream();
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, obj);
                result = ms.ToArray(); 
            
                ret = true;
            } catch (Exception e) {
                ret = false;
                Debug.Log(e.Message);
            }
    
            return ret;
        }
    
        // 反序列化数据包
        public bool Deserialize(byte[] data,out object result) {
            bool ret = false;
            result = new object();
    
            try {
                MemoryStream ms = new MemoryStream(data);
                BinaryFormatter bf = new BinaryFormatter();
                result = bf.Deserialize(ms);
            
                ret = true;
            } catch (Exception e) {
                ret = false;
                Debug.Log(e.Message);
            }
    
            return ret;
        }
    
        // 服务端变量
        TcpListener _listener;
        
        // 储存已连接客户的结构体
        private struct Client {
            public TcpClient client;
            public byte[] buffer;               // 接收缓冲区
            public List<byte> pendingData;    // 还未处理的数据
        }
    
        // 客户列表
        List<Client> _clients = new List<Client>();
    
        // 开启服务端
        private void StartServer() {
            try {
                _listener = new TcpListener(IPAddress.Any, _port);
                _listener.Start();
                _listener.BeginAcceptSocket(HandleAccepted, _listener);
    
                _isServer = true;
            } catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
    
        // 停止停止服务端
        private void StopServer() {
            try {
                _listener.Stop();
    
                // 清空客户列表
                lock (_clients) {
                    foreach (Client c in _clients) {
                        RemoveClient(c);
                    }
                    _clients.Clear();
                }
    
                // 清空数据包
                lock (_packages) {
                    _packages.Clear();
                }
                
                _isServer = false;
            } catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
    
        // 处理客户端连接的回调函数
        private void HandleAccepted(IAsyncResult iar) {
            if (_isServer) {
                TcpClient tcpClient = _listener.EndAcceptTcpClient(iar);
                Client client = new Client();
                client.client = tcpClient;
                client.buffer = new byte[tcpClient.ReceiveBufferSize];
                client.pendingData = new List<byte>();
    
                // 把客户加入到客户列表
                lock (_clients) {
                    AddClient(client);
                }
    
                // 开始异步接收从客户端收到的数据
                tcpClient.GetStream().BeginRead(
                        client.buffer,
                        0,
                        client.buffer.Length,
                        HandleClientDataReceived,
                        client);
    
                // 开始异步接收连接
                _listener.BeginAcceptSocket(HandleAccepted, _listener);
            }
        }
    
        // 从客户端接收数据的回调函数
        private void HandleClientDataReceived(IAsyncResult iar) {
            try {
                if (_isServer) {
                    Client client = (Client)iar.AsyncState;
                    NetworkStream ns = client.client.GetStream();
                    int bytesRead = ns.EndRead(iar);
    
                    // 连接中断
                    if (bytesRead == 0) {
                        lock (_clients) {
                            _clients.Remove(client);
                        }
                        return;
                    }
    
                    // 保存数据
                    for (int i=0; i<bytesRead; ++i) {
                        client.pendingData.Add(client.buffer[i]);
                    }
    
                    // 把数据解析成包
                    while (client.pendingData.Count >= _packageSize) {
                        byte[] bp = client.pendingData.GetRange(0, _packageSize).ToArray();
                        client.pendingData.RemoveRange(0, _packageSize);
    
                        // 把数据包分发给所有客户
                        lock(_clients) {
                            foreach (Client c in _clients) {
                                c.client.GetStream().Write(bp, 0, _packageSize);
                                c.client.GetStream().Flush();
                            }
                        }
                    }
    
                    // 开始异步接收从客户端收到的数据
                    client.client.GetStream().BeginRead(
                            client.buffer,
                            0,
                            client.buffer.Length,
                            HandleClientDataReceived,
                            client);
                }
            } catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
    
        // 加入客户
        private void AddClient(Client c) {
            _clients.Add(c);
        }
    
        // 删除客户
        private void RemoveClient(Client c) {
            c.client.Client.Disconnect(false);
        }
    
        // 客户端变量
        Client _server;
    
        // 连接至服务端
        private void ConnectServer() {
            try {
                TcpClient tcpServer = new TcpClient();
                tcpServer.Connect(_ip, _port);
                _server = new Client();
                _server.client = tcpServer;
                _server.buffer = new byte[tcpServer.ReceiveBufferSize];
                _server.pendingData = new List<byte>();
                
                // 异步接收服务端数据
                tcpServer.GetStream().BeginRead(
                    _server.buffer, 
                    0, 
                    tcpServer.ReceiveBufferSize,
                    HandleServerDataReceived,
                    _server);
    
                _connected = true;
            } catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
    
        // 从服务端断开
        private void DisconnectServer() {
            try {
                lock (_server.client) {
                    _server.client.Client.Close();
                }
    
                // 清空数据包
                lock (_packages) {
                    _packages.Clear();
                }
    
                // 删除所有客户人物模型
                mCharacter.RemoveAllEnemyCharacter();
    
                _connected = false;
            } catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
    
        // 从服务端读取数据的回调函数
        private void HandleServerDataReceived(IAsyncResult iar) {
            if (_connected) {
                Client server = (Client)iar.AsyncState;
                NetworkStream ns = server.client.GetStream();
                int bytesRead = ns.EndRead(iar);
          
                // 连接中断
                if (bytesRead == 0) {
                    DisconnectServer();
                    return;
                }
    
                // 保存数据
                for (int i=0; i<bytesRead; ++i) {
                    server.pendingData.Add(server.buffer[i]);
                }
    
                // 把数据解析成包
                while (server.pendingData.Count >= _packageSize) {
                    byte[] bp = server.pendingData.GetRange(0, _packageSize).ToArray();
                    server.pendingData.RemoveRange(0,_packageSize);
    
                    // 把数据转换成包然后再储存包列表
                    object obj;
                    Deserialize(bp, out obj);
    
                    lock (_packages) {
                        _packages.Add((Package)obj);
                    }
                }
    
                // 异步接收服务端数据
                server.client.GetStream().BeginRead(
                    server.buffer, 
                    0, 
                    server.client.ReceiveBufferSize,
                    HandleServerDataReceived,
                    server);
            }
        }
        
        // 发送自己的当前的状态包给服务端
        public void SendStatus(Vector3 pos, Vector3 rot,Vector3 cameraRot, 
            Vector3 rightHandRot, bool isShooted, int hp) {
            try {
                if (_connected) {     
                    Package p = new Package();
                    p.id = _id;
                    p.pos.Fill(pos);
                    p.rot.Fill(rot);
                    p.cameraRot.Fill(cameraRot);
                    p.rightHandRot.Fill(rightHandRot);
                    p.isShooted = isShooted;
                    p.hp = hp;
    
                    // 发送包到服务端
                    byte[] bp;
                    Serialize(p, out bp);
    
                    lock (_server.client) {
                        _server.client.GetStream().Write(bp, 0, _packageSize);
                        _server.client.GetStream().Flush();
                    }
                }
            } catch (Exception e) {
                Debug.Log(e.Message);
    
                // 断开服务端
                DisconnectServer();
            }
        }
    
        // 获取包
        public bool NextPackage(out Package p) {
            lock (_packages) {
                if (_packages.Count == 0) {
                    p = new Package();
                    return false;
                }
    
                p = _packages[0];
                _packages.RemoveAt(0);
            }
    
            return true;
        }
    }

    4.EnemyCharacter.cs

    这个脚本负责控制其他客户的行为,比如从服务端接收到其他客户移动或开枪的数据,就要用这个脚本来更新其他客户的当前行为

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class EnemyCharacter : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
            // 获取本机玩家的对象
            mCharacter = GameObject.Find("Character").transform;
            mCharacterComponent = mCharacter.GetComponent<Character>();
            // 获取摄像头对象
            mCamera = transform.Find("Camera");
    		// 获取右手对象
            mRightHand = transform.Find("RightHand");
            // 获取枪声播放组件
            mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
            // 获取火花效果
            mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
            // 获取脚步声播放组件
            mAudio = transform.GetComponent<AudioSource>();
            // 显示血量和ID的组件
            txID = transform.Find ("ID");
            txIDText = transform.Find ("ID").GetComponent<TextMesh> ();
            txHP = transform.Find ("HP");
    		txHPText = transform.Find ("HP").GetComponent<TextMesh> ();
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		// 摧毁对象
            if (isDestroy) {
                Destroy(gameObject);
            }
    
            // 更新对象属性
            UpdataProperties();
    	}
        
        private Transform mCharacter;
        private Character mCharacterComponent;
        private Transform mCamera;
        private Transform mRightHand;
        private AudioSource mGunAudio;
        private AudioSource mAudio;
        private ParticleSystem mFireEffect;     // 开枪后的火花
        public GameObject mPiece;       // 开枪后撞击产生的碎片
        private bool isDestroy = false;
    
        // 销毁角色
        public void Destroy() {
            isDestroy = true;
        }
    
        // 角色移动动作
        public void Move(Vector3 pos, Vector3 rot, Vector3 cameraRot, Vector3 rightHandRot) {
            if (pos != transform.position) {
                transform.position = pos;
                PlayStepSound();
            } else {
                StopPlayStepSound();
            }
    
            mCamera.eulerAngles = cameraRot;
    
            transform.eulerAngles = rot;
    
            mRightHand.eulerAngles = rightHandRot;
        }
    
        // 播放脚步声
        private void PlayStepSound() {
            if (!mAudio.isPlaying) {
                mAudio.Play();
            }
        }
    
        // 停止播放声音
        private void StopPlayStepSound() {
            if (mAudio.isPlaying) {
                mAudio.Stop();
            }
        }
    
        // 开枪
        public void Fire() {
            // 射击音效与画面
            PlayShotSound();
    
            // 播放火花效果
            PlayFireEffect();
    
            // 判断射击位置
            RaycastHit hit;
            if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
                // 被枪击中的地方会有碎片弹出
                DrawPieces(hit);
    
                // 判断本机玩家是否中枪如果是就减
                print(hit.collider.name);
                if (hit.collider.name == mCharacter.name) {
                    mCharacterComponent.GetHurt();
                }
            }
        } 
    
        // 播放枪声
        private void PlayShotSound() {
            mGunAudio.PlayOneShot(mGunAudio.clip);
        }
    
        // 画碎片
        private void DrawPieces(RaycastHit hit) {
            for (int i = 0; i < 5; ++i) {
                GameObject p = Transform.Instantiate(mPiece);
    
                // 碎片撞击到物体后的反弹位置
                Vector3 fwd = mCamera.forward * -1;
                p.transform.position = hit.point;
                p.GetComponent<Rigidbody>().AddForce(fwd * 100);
    
                // 0.3秒后删除
                Destroy(p, 0.3f);
            }
        }
    
        // 播放火花效果
        private void PlayFireEffect() {
            mFireEffect.Play();
        }
    
        // 人物变量
        private int _id = 1;
        private int _hp = 100;
        private Transform txID;
        private TextMesh txIDText;
        private Transform txHP;
        private TextMesh txHPText;
    
        // 角色id
        public void SetID(int id) {
            _id = id;
        }
    
        // 角色血量
        public void SetHP(int hp) {
            _hp = hp;
        }
    
        // 更新角色变量/属性
        private void UpdataProperties() {
            // 显示血量和ID
            txIDText.text = "ID:"+_id.ToString();
            txHPText.text = "HP:"+_hp.ToString();
    
            // 血量和ID的方向,面向着本机玩家
            txID.rotation = mCharacter.rotation;
            txHP.rotation = mCharacter.rotation;
        }
    }
    


    展开全文
  • unity编写一个简单的小游戏

    万次阅读 多人点赞 2018-12-22 21:15:01
    unity编写一个简单的小游戏简易fly bird的制作关于flybird场景的搭建和素材的导入使用素材搭建game和scene制作柱体bird的scriptcolumn的spritecolumn的生成游戏判定游戏结束画面结语 简易fly bird的制作 关于flybird...

    简易fly bird的制作

    关于flybird

    这个游戏想必大家都玩过,今天我们要使用unity来写一个差不多的fly bird,需要unity和vs的配合,这个游戏规则就是控制一个bird通过一个个柱体之间的缝隙。在这里插入图片描述

    场景的搭建和素材的导入

    打开unity,选择文件路径,因为这个游戏是2D的,选择2D后我们create。素材对于我们学生党很难搞,我们百度asset store就找到unity为我们提供素材的地方,在搜索栏搜索bird就好了,在筛选的位置选择价格免费,很方便的,在里面随便选一个bird就好了,import到你的unity项目里。觉得这个素材质量不太好的,自己可以使用ps在网图上面扣下来自己想要的bird。在这里插入图片描述

    使用素材搭建game和scene

    在你的素材包选择你喜欢的bird拖入scene中,我们要完成一个fly的bird,我们需要借用unity中的刚体,就是给bird在右侧的inspector加上脚本Rigidbody 2D。
    这样这个bird就具有我们想让他有的自然下落等一系列物理条件
    运行一下可以看到bird的自然下落,就ok了
    我们的bird在游戏里不可能是一直下落的,这样需要我们给这个场景加上一个边界。我是直接使用ps弄了一个白色柱体(你还可以使用unity自带的素材,也可以按上面导入),游戏边界我们让他有什么条件呢,让他是个类似实体平面,可以让bird停留在上面这时候使用collider2D这个属性(当然我们的bird上也必须有这个collider,里面有个polygon collider2D这个很适合这种不规则形状的物体),先对一个柱体完成上面的操作 剩下的复制(ctrl+d就可以直接复制)就好了 (横竖不一样,调节z的角度为90就OK了),将边框完成。运行一下发现bird会落在白色边界上,这样场景就搭建好了。

    制作柱体

    我们还是使用上面的白色柱体(不喜欢的可以自己随便导入柱体,没有影响的),对柱体加上collider(我们游戏规则,bird必须在两个柱体之间的缝隙通过,两个柱体是不可以穿过),ok后复制一个,调整二者的位置,形成我们想得到的样子,然后我们右键建一个gameobject并命名为column,将上面建好的两个柱体拖到这个column里面,在column上(注意不要点在那两个柱体上)增加一个box collider2D并使用edit collider,然后对这个的is trigger进行勾选(这个的意思让bird可以在这个之间的collider通过,方便我们后面编写script进行计分),让column成为这样并拖入assets中即可成为我们的素材(毕竟以后我们要不断的生成column):
    在这里插入图片描述

    bird的script

    现在开始我们快乐的edit script环节,在assets右键生成一个文件夹,命名为code,在code里面create一个c# script命名为birdcontrol(这几步最好一次完成,否则到时候加脚本到bird上会出现找不到脚本的bug,真的莫名其妙),打开这个birdcontrol,我们会看到自动生成的代码里面有两个函数(可以使用的函数有4个,每一个的具体含义自己百度,我就不赘述了)
    将下面的代码拖到bird的inspector中,对speed赋值,通过空格键,操作bird的移动。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class birdcontrol : MonoBehaviour
    {
    
        Rigidbody2D bird;
    
        public float speed;                                         //使用speed方便在项目里面选择一个合适的倍数
    
        // Use this for initialization
        void Start()                                                //游戏一开始就会调用的函数
        {
            bird = GetComponent<Rigidbody2D>();                     //将游戏里面的bird引入
        }
        // Update is called once per frame
        void Update()                                               //游戏每一帧都会调用的函数
        {
            if (Input.GetKeyDown("space"))
            {
                bird.AddForce(new Vector2(0, 2 * speed));           //vector2相当于一个有xy的矢量
            }
        }
    }
    
    

    column的sprite

    我们完成了bird在竖直方向的移动控制,接下来要完成在水平方向的移动,总所周知运动是相对的,我们可以通过让column的移动,来完成水平方向的移动,让他以一个均匀的速度自动向右移动translation。
    还有当bird通过column时会加一分,这时候使用ontriggerexit2D这个函数来判断。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class columncontrol : MonoBehaviour {
        public float speed;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
            this.transform.Translate(new Vector2(-speed,0));
    	}
    
        private void OnTriggerExit2D(Collider2D collision)
        {
            if (collision.gameObject.tag == "player")
            {
                Debug.Log("1");
            }
        }
    }
    
    

    column的生成

    游戏中不可能只有一个柱体,而且也需要他的上下变动,这时候我们需要在游戏的最右端的一个位置不断地生成位置随机的column,这时候我们需要给他一个生成点spawnpoint,右键一个empty,命名为spawnpoint,然后我们为他添加脚本

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class spawn : MonoBehaviour {
    
        public GameObject column;
        public float colddown = 2f;
        public float NextSpawn;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
            if (Time.time > NextSpawn)
            {
                NextSpawn = Time.time + colddown;
    
                Vector3 spawnP = transform.position;
                spawnP.y += Random.Range(3.0f, -3.0f);
                Instantiate(column, spawnP,transform.rotation);
            }
    	}
    }
    
    

    最后别忘记将column应用上去
    在这里插入图片描述

    游戏判定

    我们都知道当这个bird碰到墙壁,柱体上,游戏都是失败,这时候就会停止游戏,所以我们给他一个hitted和GM的脚本进行判断(hitted是要添加到ground和column上)

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    //GM.cs
    public class GM : MonoBehaviour {
    
        public static int score = 0;
    
        public static bool Active = true;
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    }
    
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    //hitted.cs
    public class Hitted : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    
        private void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.gameObject.tag == "Player")
            {
                GM.Active = false;
            }
        }
    }
    
    

    一旦bird与其碰上就会立刻停止column的运动和生成,所以我们在上面的也要相应的修改

    if (GM.Active)		//columncontrol.cs
            {
                this.transform.Translate(new Vector2(-speed, 0));
            }
    
    if (Time.time > NextSpawn&&GM.Active)	//spawn.cs
            {
                NextSpawn = Time.time + colddown;
    
                Vector3 spawnP = transform.position;
                spawnP.y += Random.Range(3.0f, -3.0f);
                Instantiate(column, spawnP,transform.rotation);
            }
    

    游戏结束画面

    右键在UI里面选择text,并把其命名为score(用于显示分数)再canvas里面再新建一个text,命名为gameover(显示Game Over!),并在上面的active位置把勾取消(不显示文本,当游戏结束时才会出现),下面是UI.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class UI : MonoBehaviour {
        public GameObject gameover;
        public Text score;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    
            score.text = "Score" + GM.score;
    
            if (!GM.Active)
            {
                gameover.SetActive(true);		//这里会让gameover显示	
            }
    	}
    }
    
    

    UI.cs要添加到canvas上并在相应的位置把score和gameover应用上去。在这里插入图片描述

    结语

    这个flybird较为简单,但是动手实践的时候会让你受益颇多,也是方便你入门的一个程序,我是用的unity和vs的组合比较方便,至于怎么配置的环境和他们之间的关系,百度一下就好了。

    展开全文
  • 新建一个项目 和场景在场景编辑器的左上角四个按钮从左到右的功能分别是 1 点住鼠标移动整体视图 热键为: q 2 移动某个物体在三维坐标系的坐标 热键为: w 3 物体的角度旋转 热键为: e 4 物体的放大 热键为: r ...
  • Unity3d制作简单拼图游戏源码+工程

    千次下载 热门讨论 2020-07-28 23:33:34
    Unity3d制作的简单拼图游戏 制作流程可以看我的博客:http://blog.csdn.net/cube454517408/article/details/7907247
  • unity3D简单的汽车游戏

    千次阅读 2012-04-15 15:22:34
    unity3d做的一个简单的汽车游戏,对汽车属性的一些调节我现在还没搞明白了,所以就只做一个简单的demo了,呵呵~~~~我们老师说做游戏一定要会抠图,的确如此,由于我工程中导入的模型多了,文件过大,我没把工程传...
  • Unity3D游戏开发之SQLite让数据库开发更简单

    万次阅读 多人点赞 2015-11-03 08:09:40
    在经历了一段时间的忙碌后,博主终于有时间来研究新的东西啦,今天博客向和大家一起交流的内容是在Unity3D游戏开发中使用SQLite进行数据库开发,坦白来讲,在我的技术体系中Web和数据库是相对薄弱的两个部分,因此...
  • 今天我们来说说通过反编译Unity3D的AssetBundle来提取游戏资源,博主写这篇文章的目的并非是要教大家如何去破解一款基于Unity3D引擎开发的游戏,而是想通过今天这篇文章来告诉大家如何在开发Unity3D游戏的过程中保护...
  • Unity3D游戏开发之使用Unity3D开发2D游戏 (一)

    万次阅读 热门讨论 2014-03-17 12:38:57
    今天要和大家分享的是基于Unity3D开发2D游戏,博主一直钟爱于国产武侠RPG,这个我在开始写Unity3D游戏开发系列文章的时候就已经说过了,所以我们今天要做的就是利用Unity3D来实现在2D游戏中人物的走动控制。...
  • unity3d简单射击游戏

    千次阅读 2017-07-03 11:30:56
    一个简单的射击方块的游戏 链接:http://pan.baidu.com/s/1nuThGI9
  • [Unity3D]Unity3D游戏开发之截屏保存精彩瞬间

    万次阅读 热门讨论 2014-09-10 20:37:27
    作为一名热爱单机游戏的玩家,博主每次在玩游戏的同时截取游戏中比较喜欢的画面,特别是学习了Unity3D以后玩游戏的时候更多地是从一个游戏设计者的角度来看待游戏,换句话说,可能关注技术的成分更多一点吧。...
  • [Unity3D]Unity3D 游戏开发之碰撞检测

    万次阅读 2014-06-13 21:10:37
    大家好,欢迎大家关注由我为大家带来的Unity3D游戏开发系列文章,我的博客地址为:http://blog.csdn.net/qinyuanpei。 今天我们来一起来学习Unity3D中一个很重要的概念:碰撞。为什么说碰撞很重要呢?因为在游戏中...
  • Unity3d实战之Unity3d网络游戏实战篇(1):坦克构建 阅读文章前请细读以下几点: 学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版。 本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码...
  • 掌握Unity3D基本元素 1.1 简单游戏 1.1.1在场景中创建一个立方体 1.1.2编写可以使立方体运动的程序 1.1.3测试游戏1.1.4总结1.2 资源导入1.3 山体系统1.4 灯光1.5 材质1.6 预设1.6.1制作预设1.6.2例子1.7 声音...
  • Unity 3D 网络游戏架构设计

    千次阅读 2019-07-05 10:04:13
    本课程是 Unity 3D 系列教程,目标是带领读者搭建一个商业游戏的网络架构设计,该架构设计是游戏的核心技术,将采用 Unity 2017.2 最新版本作为开发工具。内容分为 UI 架构、技能架构、服务器和网络同步四大部分,共...
  • unity3d游戏源码

    热门讨论 2020-07-17 17:53:37
    一款用unity3d编写的andriod小游戏的源码,希望可以给大家可以提供参考
  • [Unity3D]Unity3D游戏开发之自由视角下的角色控制

    万次阅读 热门讨论 2014-09-10 12:54:34
    在上一篇文章[Unity3D]Unity3D游戏开发之角色控制漫谈>一文中,博主与大家分享自己在角色控制方面的一些感悟。今天呢,我们继续来探讨Unity3D角色控制的内容,今天博主将解决在上一篇文章中没有解决的问题,即自由...
1 2 3 4 5 ... 20
收藏数 21,202
精华内容 8,480
关键字:

unity3d 简单游戏