【C#+Unity小白】制作一款简单的2D平台游戏,PONG(弹球)游戏

news2025/9/20 8:31:15

素材均来自于Brackeys

各位可以到Youtube支持他,他的视频底下有他个人的网站,各位可以从他拿素材。

像我这样的菜鸡,在我看codeMonkey零零散散的制作视频而不知所措时,Brackeys出现了,他耐心教导,他真的是太温柔了,我哭死

另外该游戏,他是用JS写的游戏脚本,而我改用C#,接下来我会完整地展现如何制作这款简单的游戏,以便各位像我一样的初学者熟悉unity的操作,不过我不会像他的视频一样冗长。

游戏效果

请添加图片描述

玩法

就两个玩家互相对撞,碰球,计分产生音效等等。

下载链接

链接:https://pan.baidu.com/s/1BFLubNjFuGdtBEV66pYUSw
提取码:gbjr

Unity的操作

创建C# 代码

接下来由chatGpt讲解它们的一个实现逻辑。
在这里插入图片描述

BallControl.cs

让球在等待几秒后,随机以任意方向抛出,控制随着到player的不同,碰撞后反弹的影响。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallControl : MonoBehaviour
{
    public float ballSpeed = 100f;
    private float xVel;
    void Start()
    {
        StartCoroutine(WaitAndGoBall(2f));
    }

    void Update()
    {
        xVel = GetComponent<Rigidbody2D>().velocity.x;
        //Debug.Log("Velocity:" + xVel);
        if(xVel <18f && xVel > -18f && xVel!=0)
        {
            if(xVel > 0)
            {
                GetComponent<Rigidbody2D>().velocity = new Vector2(20f, GetComponent<Rigidbody2D>().velocity.y);
            }
            else
            {
                GetComponent<Rigidbody2D>().velocity = new Vector2(-20f, GetComponent<Rigidbody2D>().velocity.y);
            }
            //Debug.Log("Velocity Before " + xVel);
            //Debug.Log("Velocity after " + GetComponent<Rigidbody2D>().velocity.x);
        }
    }

    void OnCollisionEnter2D(Collision2D colInfo)
    {
        //CompareTag 不能 == ,因为tag属性不能被检查 
        if (colInfo.collider.CompareTag("Player"))
        {
            GetComponent<Rigidbody2D>().velocity = new Vector2(
            GetComponent<Rigidbody2D>().velocity.x,
            GetComponent<Rigidbody2D>().velocity.y / 2 + colInfo.collider.GetComponent<Rigidbody2D>().velocity.y / 3
            );
            GetComponent<AudioSource>().pitch = Random.Range(0.8f, 1.2f);
            GetComponent<AudioSource>().Play();
        }
    }

    void GoBall()
    {
        float randomNumber = Random.Range(0f, 1f);
        if (randomNumber <= 0.5f)
        {
            GetComponent<Rigidbody2D>().AddForce(new Vector2(ballSpeed, 10f));
        }
        else
        {
            GetComponent<Rigidbody2D>().AddForce(new Vector2(-1* ballSpeed, -10f));
        }
    }

    void ResetBall()
    {
        GetComponent<Rigidbody2D>().velocity = Vector2.zero;
        transform.position = Vector2.zero;
        StartCoroutine(WaitAndGoBall(0.5f));
    }

    IEnumerator WaitAndGoBall(float waitTime)
    {
        yield return new WaitForSeconds(waitTime);
        GoBall();
    }
}

GameManager.cs

设置GUI,计分

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static int playerScore01 = 0;
    public static int playerScore02 = 0;

    public GUISkin theSkin;

    private Transform theBall;

    void Start()
    {
        theBall = GameObject.FindGameObjectWithTag("Ball").transform;    
    }

    public static void Score(string wallName)
    {
        if (wallName == "rightWall")
        {
            playerScore01 += 1;
        }
        else
        {
            playerScore02 += 1;
        }
        //Debug.Log("Player Score 1 is " + playerScore01);
        //Debug.Log("Player Score 2 is " + playerScore02);
    }
    
    void OnGUI()
    {
        GUI.skin = theSkin;
        GUI.Label(new Rect(Screen.width / 2 - 150-18, 25, 100, 100), "" + playerScore01);
        GUI.Label(new Rect(Screen.width / 2 + 150-18, 25, 100, 100), "" + playerScore02);
    
        if(GUI.Button(new Rect(Screen.width / 2 -121/2, 35, 121, 53), "RESET"))
        {
            playerScore01 = 0;
            playerScore02 = 0;

            theBall.gameObject.SendMessage("ResetBall");
        }
    }
    
}

GameSetup.cs

固定player位置和设定边界

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameSetup : MonoBehaviour
{
    //Reference the camera
    public Camera mainCam;

    //Reference the colliders we are going to adjust
    public BoxCollider2D topWall;
    public BoxCollider2D bottomWall;
    public BoxCollider2D leftWall;
    public BoxCollider2D rightWall;

    //Reference the players
    public Transform Player01;
    public Transform Player02;

    //check
    public float po = 180f;
    void Start () 
    {
        //Only set this to Update if you know the screen size can change during a playsession.
        //Move each wall to its edge location:
        topWall.size = new Vector2 (mainCam.ScreenToWorldPoint (new Vector3 (Screen.width * 2f, 0f, 0f)).x, 1f);
        topWall.offset = new Vector2 (0f, mainCam.ScreenToWorldPoint (new Vector3 (0f, Screen.height, 0f)).y + 0.5f);

        bottomWall.size = new Vector2 (mainCam.ScreenToWorldPoint (new Vector3 (Screen.width * 2, 0f, 0f)).x, 1f);
        bottomWall.offset = new Vector2 (0f, mainCam.ScreenToWorldPoint (new Vector3(0f, 0f, 0f)).y - 0.5f);

        leftWall.size = new Vector2(1f, mainCam.ScreenToWorldPoint(new Vector3(0f, Screen.height*2f, 0f)).y);
        leftWall.offset = new Vector2(mainCam.ScreenToWorldPoint(new Vector3(0f, 0f, 0f)).x - 0.5f, 0f);

        rightWall.size = new Vector2(1f, mainCam.ScreenToWorldPoint(new Vector3(0f, Screen.height*2f, 0f)).y);
        rightWall.offset = new Vector2(mainCam.ScreenToWorldPoint(new Vector3(Screen.width, 0f, 0f)).x + 0.5f, 0f);

        //Move the players to a fixed distance from the edges of the screen:
        Player01.position = new Vector3(mainCam.ScreenToWorldPoint (new Vector3 (po, 0f, 0f)).x, Player01.position.y, Player01.position.z);
        Player02.position = new Vector3(mainCam.ScreenToWorldPoint (new Vector3 (Screen.width -po, 0f, 0f)).x, Player02.position.y, Player02.position.z);
    }   
}

PlayerController.cs

控制移动方向与速度

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public KeyCode moveUp;
    public KeyCode moveDown;
    public float speed = 10f;

    private Rigidbody2D rb2D;

    // Start is called before the first frame update
    void Start()
    {
        rb2D =  GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(moveUp))
        {
            rb2D.velocity = new Vector2(0, speed);            
        }else if (Input.GetKey(moveDown))
        {
            rb2D.velocity = new Vector2(0, speed*-1);
        }
        else
        {
            rb2D.velocity = Vector2.zero;
        }
    }
}

SideWall.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SideWall : MonoBehaviour
{
    void OnTriggerEnter2D(Collider2D hitInfo)
    {
        if (hitInfo.name == "Ball")
        {
            string wallName = transform.name;
            GameManager.Score(wallName);
            GetComponent<AudioSource>().Play();
            //发送消息
            hitInfo.gameObject.SendMessage("ResetBall");
        }
    }
}

创建目录

在这里插入图片描述

  • Audio:音频文件(Brackeys的素材)
    在这里插入图片描述

  • Button:RESET的按钮效果(Brackeys的素材)
    在这里插入图片描述

  • Scenes:unity自带

  • Script:C#脚本存放位置

  • Textures:贴图,存放游戏两块白小板和背景图(Brackeys的素材)

  • Thaleah-PixelFont:unity素材商城导入的字体文件(免费)

sorting Layer(目的:避免背景遮挡操控的游戏对象)

在这里插入图片描述
将他们拖入到Scene中。

调整

命名GameObject
在这里插入图片描述
调整 GameObjectBG、Ball、Main Camera的位置如下,以便让摄像头可以居中显示。
在这里插入图片描述

设定图层

点击任意一个对象,到该Sprite组件(没有的话,就Add Component该组件
在这里插入图片描述
在该组件下的操作界面点击Add Sorting Layer。
在这里插入图片描述

创建Layers的排序如下
在这里插入图片描述

后面让背景的sortings layerBackground就行了,而玩家要操作的player1player2的图层则为GO

另外让Inspector检查背景对象的Layer,当然由于弄的并不是太过于复杂,其他的GameObject并不需要这样干,默认即可。
在这里插入图片描述

player01player02

加上Tag,没有就自己在Add Tag找位置加入

在这里插入图片描述

碰撞效果

Size这样设置,才方便设定球Ball能够撞击到player的范围多大为有效。
在这里插入图片描述

移动按钮与移动速度

PlayerController.cs拉进去,分别设置如下
在这里插入图片描述
在这里插入图片描述

设定质量和惯性之类

mass设置的足够大,这样可以让ball碰到player时,不至于让player发生旋转或者位移的情况(相当于固定在X轴了)。
在这里插入图片描述

Ball

拉入脚本BallControl.cs先,调速度。
在这里插入图片描述

打上标签,方便程序识别

在这里插入图片描述

添加碰撞组件

碰撞还要能反弹到一定的距离
Projects栏中的Assets/Textures,右键
在这里插入图片描述

命名如下,设置的参数可以自己调。
在这里插入图片描述
起碰撞的区域是有多大也自己调
在这里插入图片描述

碰撞音效

在这里插入图片描述

Mass自行调整

越小,受反弹后的速度的变化越快。
在这里插入图片描述

游戏管理器_GM(判断游戏输赢或者重开?)

鼠标右键,创建名为_GMGameObject
在这里插入图片描述

设置ScrollSkin

Project栏的Assets/Textures文件目录中,右键

在这里插入图片描述
命名为ScrollSkin,接着如下图设置,定义字体、字体大小、按钮点击效果等。
在这里插入图片描述
字体font则是在商场中,导入的文件获取的,自己登录搜索查找即可。
在这里插入图片描述

背景音乐

注意不能太大和尖锐,需要自己调
在这里插入图片描述

创建边界

同样地创建四个GameObject,分别代表上下左右边界,拉入到_GM对象
在这里插入图片描述
Add Component组件,表示成能够被碰撞的物体。
在这里插入图片描述
将C#脚本SideWall拉入到leftWallrightWall,同时创建Audio Source组件,音源是Audio文件夹下的Bonus,用来判断谁胜谁败了,以便之后让游戏管理的脚步计分。
在这里插入图片描述
GameSetup.csGameManager.cs拉入到_GMInspector栏,设置如下。
在这里插入图片描述
这样在游戏运行时,就能看到顶部的RESET按钮的点击效果和点击Gizmos时显示边框效果
在这里插入图片描述

打包游戏

在这里插入图片描述
如果是switch选项的话,就照点,加载后再点击Build选择。
在这里插入图片描述

问题

打包的版本问题

尽管我看了不少Youtube的视频创作者都推荐跟它的版本一样,但是谁管呢?

我用的版本是

  • Unity 2021.3.20f1c1

当然不听老人言,吃亏在眼前,我也遇到了麻烦:

  • 操作界面的位置发生变化
  • 新版去掉了分辨率选择窗口

重启按钮

重启只是让球回到了原来的位置,但是没有改变player

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411585.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2023最新】超详细图文保姆级教程:App开发新手入门(1)

1. 关于 『YonBuilder移动开发』 PS: 本段内容主要目的是去除官方语音描述&#xff0c;用通俗的语言来简单介绍一下『YonBuilder移动开发』&#xff0c;方便新手开发者判断是否适合自己上手学习。 简介&#xff1a; YonBuilder移动开发平台(官网&#xff1a;https://develope…

2023年14界蓝桥杯省赛“日期统计”题解

问题描述 小蓝现在有一个长度为 100 的数组&#xff0c;数组中的每个元素的值都在 0 到 9 的范围之内。数组中的元素从左至右如下所示&#xff1a; 5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 …

Python 小型项目大全 41~45

四十一、幸运星 原文&#xff1a;http://inventwithpython.com/bigbookpython/project41.html 在这个碰运气游戏中&#xff0c;你掷骰子来收集星星。你掷得越多&#xff0c;你能得到的星星就越多&#xff0c;但是如果你得到三个头骨&#xff0c;你就失去了一切&#xff01;这款…

【网络编程】UDP

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; 目 录&#x1f3ef;一. UDP数据报套接字编程&#x1f3f0;二. 写一个 UDP 版本的 回显服务器-客户端.&#xff08;echo server&#xff09;&#x1f3ed;1. 服务器&#xff1a;&…

咖啡卷到现在,他们开始往里面掺北京豆汁了

咖啡卷到现在&#xff0c;他们开始往里面掺北京豆汁了0. 导读1. 人手一杯&#xff0c;果咖大势所趋2. 双倍成瘾&#xff1a;茶咖和酒咖被重点推荐3. 地方小吃融入咖啡&#xff0c;比如北京豆汁4. 炙手可热的云南咖啡豆5. 咖啡、户外和环保&#xff1a;绑定可持续6. 小结0. 导读…

vue - 【最新】简单快捷的 element ui 组件库的主题色更改,批量统一覆盖替换解决方法,无需npm装包。(适用于新手小白的方法,很简单)

效果图 最新解决方案,简单便捷且不用npm安装任何第三方包就能搞定。 原来的主题色是蓝色 ,可以通过本篇博客提供的方法,统一变成其他主题颜色,比如下面的紫色: 下面就是真实的运行效果,保证可行~ 这样就不用每个组件单独去写样式覆盖颜色了! 定制主

干雾抑尘系统的降尘工作原理

对于干雾抑尘系统&#xff0c;相信大家现在都不陌生&#xff0c;作为工业无组织排放粉尘治理的常用除尘产品&#xff0c;其有着传统喷雾降尘设备无法比拟的优势。今天我们就来了解下&#xff0c;干雾抑尘系统到底是什么&#xff1f;他的工作原理到底是怎样的呢&#xff1f; ​…

进程、线程、协程的区别

目录 1、什么是进程&#xff1f; 2、什么是线程&#xff1f; 3、什么是协程&#xff1f; 4、进程、线程、协程的区别 (1) 进程、线程的区别 (2) 线程、协程的区别 1、什么是进程&#xff1f; 简单来说&#xff0c;进程可以看做是加载到内存里的程序&#xff0c;实际上一…

Qt Quick - ColorDialog 颜色对话框

ColorDialog 颜色对话框使用总结一、概述二、实现的要点三、一些例子的合集1. 按钮点击修改按钮文字颜色一、概述 ColorDialog 就是Qt 提供的标准的颜色对话框。ColorDialog 允许用户选择颜色。 对话框最初是不可见的。你需要首先按需设置属性&#xff0c;然后将visible设置为…

前端 icon 方案该升级了

说到 icon&#xff0c;很多前端开发只能想到 iconfont&#xff0c;或者组件库中提供的那些图标&#xff0c;当然这对于绝大多数开发场景是够用的。 但要知道&#xff0c;iconfont 的方案其实是在 2016 年公开&#xff0c;到现在也已经有 6 年之久&#xff0c;它确实在这一段时…

【产品经理】为什么你画的流程图开发总说看不懂?

在做产品设计时&#xff0c;会碰到非常多的业务流程&#xff0c;当我们需要跟开发解释一个相对复杂的流程时&#xff0c;一个清晰的流程图&#xff0c;便非常重要了。 我们做产品设计的时候&#xff0c;会碰到非常多的业务流程&#xff0c;有简单的&#xff0c;有复杂的&#x…

「业务架构」需求工程——捕获与分析(第二部分)

从用户、客户和其他涉众那里生成系统需求的活动。可行性研究结束后&#xff0c;我们进入下一个阶段;抽取和分析。需求捕获和分析它是一个与客户和最终用户交互的过程&#xff0c;以查明领域需求、系统应该提供什么服务以及其他约束。领域需求反映了系统运行的环境&#xff0c;因…

HTML5 <del> 标签、HTML5 <dd> 标签

HTML5 <del> 标签 实例 一段带有已删除部分和新插入部分的文本&#xff1a; <p>My favorite color is <del>blue</del> <ins>red</ins>!</p> 尝试一下 浏览器支持 所有主流浏览器都支持 <del> 标签。 标签定义及使用说明 …

什么是设计模式?

文章目录01 | 概念02 | 要素03 | 分类04 | 总结“每一个模式描述了一个在我们周围不断重复发生的问题&#xff0c;以及问题的解决方案的核心。这样&#xff0c;你就能一次又一次地使用该方案而不必做重复劳动”。 设计模式的核心&#xff1a;提供了相关问题的解决方案&#xff…

机械硬盘提示格式化的常见原因|3种数据恢复方法

机械硬盘是电脑存储设备中的一种&#xff0c;它的存储容量比较大&#xff0c;通常采用机械运动的方式记录数据。 其内部结构由多个盘片和磁头组成&#xff0c;盘片层层叠放在一起并通过主轴转动&#xff0c;磁头则通过磁臂上下移动并接触盘片表面进行读写操作。但是在日常使用电…

如何用Golang处理每分钟100万个请求

用Golang处理每分钟100万个请求 转载请注明来源&#xff1a;https://janrs.com/9yaq 面临的问题 在我设计一个分析系统中&#xff0c;我们公司的目标是能够处理来自数百万个端点的大量POST请求。web 网络处理程序将收到一个JSON文档&#xff0c;其中可能包含许多有效载荷的集合…

Spring Boot基础学习之(十六):用户认证和授权

本次项目使用静态资源代码免费下载 https://download.csdn.net/download/m0_52479012/87679062?spm1001.2014.3001.5501 在日常的登录网页中&#xff0c;是怎么分辨那些用户是具有那种权限呢&#xff1f; 本次博客实现的功能是&#xff1a;哪些网页是谁有可以访问的&#xff…

进程状态概念详解!(7千字长文详解)

进程状态概念详解 文章目录进程状态概念详解进程状态为什么会有这些状态运行总结阻塞就绪/新建挂起阻塞和挂起的区别&#xff01;linux下的进程状态运行——R睡眠——S暂停——T关于号深度睡眠——D追踪暂停 ——t死亡——X僵尸——Z僵尸进程的危害总结孤儿进程——S状态进程优…

一位腾讯在职7年测试工程师的心声...

作为一个在腾讯工作7年的测试工程师&#xff0c;今天就来聊聊腾讯工作压力到底从何而来。 压力的开始&#xff1a;时间回到7年前&#xff0c;我人生中的第一份实习工作&#xff0c;是在腾讯公司做一个自动化测试工程师。当时的我可谓意气风发&#xff0c;想要大干一场&#xf…

SpringBoot 介绍

1.简介 SpringBoot最开始基于Spring4.0设计&#xff0c;是由Pivotal公司提供的框架。 SpringBoot发展史&#xff1a; 2003年Rod Johnson成立Interface公司&#xff0c;产品是SpringFramework2004年&#xff0c;Spring框架开源&#xff0c;公司改名为Spring Source2008年&…