ð。在游戏中引入实时天气。
#教程 #java #gamedev #unity3d

ð

你好,伙计们,一切好吗?我脑海中有一段时间,我仍然不知道我要迈出哪个方向。我不希望它只是我在这个个人项目中工作的样本,与此同时,我认为详细的教程将是巨大的。因此,我将尝试合并两者,并在GitHub上复制我已经在readme.md中所做的一些事情,但是足够的漫长!

首先,我会尝试将发生的事情与我们的情况进行上下文化。当我经过客厅时,我正在去健身房的路上,一个朋友正在观看一段从塔科夫逃脱的视频。他立即评论了游戏在塔科夫开始下雨时下雨的特征。这给了我火花,我记得我的第一个回答是:“我可以做到!”

也就是说,我们将在下面看到的是过度工程的情况。即使它有效,所有这些都非常专注于我手头上的东西,我只是想实施与报道的类似的东西。

由于我在这里放了很多文字,所以我认为这是公平的 - 并试图保持您的注意 - 以展示我们要构建的内容。

WeatherAPI - Demo do projeto

ð£入门

随着这个想法的种植,已经内部化,我将使用spring-boot创建API,我需要决定要构建哪种引擎。一段时间以来,我一直在研究虚幻引擎,但是由于我在 Unity 中有更多的专业知识,而且由于我发现了这个AMAZING weather system,所以我选择了我们的最后一个选择。

我只需要找到适合我们问题的可靠且免费的天气数据来源。这是WeatherAPI进来的地方 - 我必须说实话,我不记得我是否在找到该解决方案之前或之后想到了名称,但事实证明两者都是一样的。由于这只是一个投资组合,所以我认为保留它并不糟糕。

显理解要求

我的第一步是在 Unity Hub 中启动一个2D项目,在上面的段落中导入上述资产,然后打开示例场景(位于场景中)。如果您不确定该怎么做,建议您跟随these steps

使用粒子设置了大约几分钟后,我去理解了它们触发的是什么。在“层次结构”选项卡中查看我们的对象,我们可以准确识别滑块的位置,并通过“检查器”选项卡,各自的触发器。

Objects and trigger of the selected object.

由于这是Slider组件,因此已知其控制属性是:

  • minvalue-滑块可以达到的最小值;
  • maxValue-滑块可以达到的最大值;
  • 值 - 滑块的当前值。

观察上面的图像,我们可以确定最小值和最大值分别为0和1。因此,定义我的API必须以这种格式具有响应。

关于引擎,目前,我们只需要了解将消耗哪些字段即可。由于我不会对本演示文稿进行任何更改,因此我将完全遵循已经开发的内容。

  • 主 - 总体强度,我们将始终设置为1;
  • 雨 - 雨的强度,将由API传递;
  • 风 - 风强度,将由API传递;
  • 雾 - 雾的强度,将由API传递。
  • 闪电 - 闪电的强度,将由API传递。

ðclatingAPI(百胜,百胜)

在提供我们的服务之前,让我们消耗WeatherAPI并了解我们将处理的数据。这很简单:只需访问网站并创建一个帐户;在这些步骤之后,您会注意到您已免费获得Pro版本的14天,但是即使没有收费,免费版本也将满足100000%的需求。

在仪表板上,将有一些用法信息以及指向documentation的链接。立即,我们呼吁“当前天气”,该地址由http://api.weatherapi.com/v1/current.json给出,强制性参数为(您生成的键)和 Q (选择的城市)。以下是一个示例调用,我将邮递员用作CLI。

Example of API call.

为什么这很重要?因为我们需要知道以后必须绘制的哪些属性。这样,整个响应将成为我们项目的课程。

观察这个json,我们有一些很酷且重要的信息,例如:

  • 条件 - 演示天气状况的对象。
  • wind_kph-风速为km/h。

让我们更加注意“ 条件”,因为它具有代码属性,根据documentation,它显示了天气状况。在文档中,对此数据有所有可能的响应。保留此信息,因为以后将非常重要。

ð让我们弄脏双手

还记得我谈论spring-boot的时候吗?好吧,为了使我们的生活更轻松,我们将使用spring initializr。如果您想使用我的配置,则如下所示(请注意依赖关系要求)。

Spring Configuration

只需下载生成的代码并将其导入您的选择IDE(我正在使用Intellij的社区版本)。切记使用适当版本配置Java。下一步将要求我们弄脏双手,所以它已经变得有趣了!

1.创建响应

第一个构建(导入它是自动的),我们可以开始玩耍。首先,我将创建一个名为“域”内部“ain.java。$ trifact”的文件夹,另一个称为“响应”。

接下来,我将创建类(因为我们已经在“ ð”中定义了其属性,以“ WeatherSponse”的名称定义了“理解要求”部分)具有属性(所有这些都将属于“双”类型)及其各自的getters/setters。以下是图像,以防您要比较。

Project with newly created response.

2.映射外部API

好吧,现在到了一个棘手的时刻...我们将从我们的呼吁转换为四个DTOs conditiondto currentdto locationdto weathdto 。每个字段的类型都可以在call usage guide中找到,其中“十进制”可以安全地使用float/double。要继续使用组织模式,我创建了“ DTO”文件夹“域”。

JSON transformed into DTOs

3.创建控制器

好吧,现在我们有足够的东西来构建我们的 controller 类。在“域”文件夹之外,我们创建“控制器”文件夹,并在其内部使用WeatherController类。在这里,我们将开始使用一些春季注释,正如我提到的,如果我经历了该项目的所有要点,我相信它将很长(更多?ð)。所以,我将离开link to the code并解释一下。

基本上,我们是:

  • 实例化我们要调用的URL;
  • 映射路径 /天气以触发我们的API; < /li>
  • 将“城市”和“ apikey”设置为强制性参数;
  • 接收响应并将其转换为对象;
  • 返回服务方法,我们将对象作为参数传递。

您很可能会看到一条错误消息,说“ WeatherService”不存在,这与现实是一致的 - 毕竟,我们尚未创建它。这样,让我们​​继续下一步并创建“魔术”将发生的班级!但是在此之前,还有一张照片。

WeatherService Error

4.看服务

好吧,是时候实际编码一些东西了!在与“域”和“控制器”相同的级别上,我们将创建我们的“服务”,后来我们将创建“ WeatherService”类。就像以前一样,我将复制代码并总结其功能。

getweather()
我们的主要功能,在其中设置响应的所有字段并返回它。如果您现在将其添加到代码中,由于没有其他功能,几行将出现错误。

public static WeatherResponse getWeather(WeatherDto weatherDto) throws ParseException {
    CurrentDto currentDto = weatherDto.getCurrent();
    LocationDto locationDto = weatherDto.getLocation();

    WeatherResponse response = new WeatherResponse();
    response.setRain(codeToRain(currentDto.getCondition().getCode()));
    response.setWind(windKphToScale(currentDto.getWind_kph()));
    response.setFog(codeToFog(currentDto.getCondition().getCode()));
    response.setLightning(codeToLightning(currentDto.getCondition().getCode()));

    return response;
}

windkphtoscale()
我跳过了一条线,是的……这是因为我将立即解释所有以“代码”开头的人,所以最好把它们留给最后一个,因为我们会花更多的努力。

我们需要将收到的数据转换为1000(千)的一部分,因为我们的 slider 的值从0(零)到1(一个),有3(3)个小数位。我进行了一些研究,并注意到61公里/小时的风实际上是“强风” - Beaufort Scale 7-的限制,这是我们项目中的颗粒所在的地方。因此,我们要做的就是将一个值划分为另一个值,并将其定向仅占前3个小数。

作业:如果我们收到的价值大于61,会发生什么?应该有什么要处理的吗?

private static double windKphToScale(double windKph) {
    int maxWindKph = 61;
    double windInScale = windKph / maxWindKph;
    String removeWindDecimalPlaces = String.format("%.3g", windInScale).replace(",", ".");

    return Double.parseDouble(removeWindDecimalPlaces);
}

codetorain()&& codetofog()
我知道仍然缺少一个!只是通过解释这些逻辑,另一个将非常容易理解,我们甚至会以不同的方式进行。还记得我要您牢记存储的“条件”对象的信息吗?好吧,我们现在将使用它!除了API的这一部分外,没有什么可以表明天气的特征,因此JSON显示所有可能的响应将用于克服此问题。

我要求chatgpt阅读同一文本,然后将其变成一张从晴朗的天气分开的桌子。我的结果很好,我可以更加完善它,但是我认为它已经足够了。

ChatGTP

我在“域”中创建了一个新文件夹,称为“枚举”和类“ weatherconditionenum”。我将其分为6个不同的陈述,这就是结果。

public enum WeatherConditionEnum {
    CLEAR,
    LIGHT_RAIN,
    MODERATE_RAIN,
    HEAVY_RAIN,
    THUNDER_RAIN,
    UNDEFINED
}

再次,我在“域名”中创建了一个新文件夹,称为“ Enums”和类“ WeatherConditionEnum”。我将其分为6个不同的陈述,这就是结果。

此外,在“域”文件夹中,我创建了文件夹“ utils”和类“ WeatherGroup” - 上面的枚举将被映射。除映射外,我们还将创建一种返回传递“代码”的特定枚举的方法。由于文本大小最大,我必须删除代码段,但您可以通过单击此link找到它。

两个步骤将相对于“雾”,utilsenum重复。

创建了所有这些,我们现在可以继续进行我们的功能。我们将在这里做的是一个开关,我们将通过“条件”的“代码”作为参数,然后返回“案例”,我们将在分组类中调用我们的方法,每种情况都会有一个值在0到1之间要返回。看起来像这样:

private static double codeToRain(int code) {
    return switch (groupWeatherCondition(code)) {
        case CLEAR, UNDEFINED -> 0.0;
        case LIGHT_RAIN -> 0.3;
        case MODERATE_RAIN -> 0.5;
        case HEAVY_RAIN -> 0.85;
        case THUNDER_RAIN -> 1;
    };
}
private static double codeToFog(int code) {
    return switch (groupFogCondition(code)) {
        case UNDEFINED -> 0.0;
        case MIST -> 0.5;
        case FOG -> 1;
    };
}

我们有了它,我们的方法现在正常工作!

codeTolightning()

正如我所说的,在上面解释的那样,这变得非常容易。使用ENUM和MAP非常有助于使项目可扩展,但是,如果您有很少的东西可以映射并且不想进行所有这些工作,该怎么办?这就是我们接下来看到的。只有两个代码导致雷暴,这就是为什么我们将它们设置为 hard coded

private static double codeToLightning(int code) {
    return switch (code) {
        case 1273 -> 0.5;
        case 1276 -> 1;
        default -> 0.0;
    };
}

我们定义了将返回大于0(零)的数字的两种情况,如果代码传递不是两者之一,则将属于“默认值”。

5.测试1、2、3-测试1、2、3

这是一个美好的时光!时间:一切都崩溃了;房子倒塌了;孩子哭泣,母亲看不到。您发现躺在厨房的最后一块面包落在地面上,甚至将黄油放下;;;;; ;;

好消息!现在是时候看到您的努力了。让我们运行我们的项目,这样做很简单。只需在IDE中找到“ gradle”选项卡,然后按照路径任务 - > Application-> bootrun;或只需打开命令窗口并运行“ gradle bootrun”命令。

bootRun task

该项目应迅速运行,您将看到下面像我的终端,或者部分喜欢它。

bootRun console

现在,您可以转到您喜欢的API客户端,并致电http://localhost:8080/weather传递参数“ City”和“ Apikey”,以确认一切都在起作用!如果一切顺利,您应该看到与此类似的回应。

bootRun Response

ð¶

我们的API完全完成,我们只需要与团结进行交流。为此,让我们回到程序中的“层次结构”选项卡,然后创建一个新的空对象 - 我将其称为callweatherapi。在此对象中,我们将添加一个脚本组件(我选择了名称apihelper,但请随时更改它),这会触发我们的呼叫。

using System.Net.Http;
using UnityEngine;

public class APIHelper : MonoBehaviour {

    [Header("Request")]
    public string cityName;
    public string ownKey;

    private void Start() {
        GetData(cityName, ownKey);
    }

    async void GetData(string city, string apiKey) {
        string API_URL = "http://localhost:8080/weather?city=" + city + "&apiKey=" + apiKey;
        using (var httpClient = new HttpClient()) {
            var response = await httpClient.GetAsync(API_URL);
            if (response.IsSuccessStatusCode)
                Debug.Log(await response.Content.ReadAsStringAsync());
            else
                Debug.Log(response.ReasonPhrase);
        }
    }
}
  • 我们将城市名称和钥匙实例化为公开,因此我们可以在引擎中具有可见性;
  • 我们创建API消耗方法;
  • 如果成功,响应将在控制台中打印;
  • 如果发生错误,将打印错误的原因。

请注意,目前我们对收到的响应没有用,因此我们需要对其进行处理。为此,我们将创建一个具有我们收到的属性的类(此类将与我们的Weathersponse一样,但在Unity之内)。引擎中创建的C#文件应看起来像下面的代码。

using System;

[Serializable]
public class WeatherAPI_Response {
    public float rain;
    public float wind;
    public float fog;
    public float lightning;
}

现在,我们只需要进行一些小调整,首先要将我们的滑块的所有“ valuechange”更改为“编辑和运行时”;

Unity Hud

然后,我们将在Apihelper类中调整一些内容,例如:

  • 接收滑块组件;
  • 通过检索组件来实例化;
  • 改进我们的呼叫方法将JSON转换为类;
  • 创建一种根据我们从API收到的内容更改滑块值的方法。

再次,由于尺寸的限制,我不得不删除最终代码段,但是您可以通过clicking here找到它。

ð弹出香槟!

如果您已经做到了这么远,我并不疯狂,机器并没有接管所有内容,使编程语言不再起作用,那么您今天可能已经完成了很棒的事情!而且我什至没有谈论这个项目,我在谈论您将注意力放在文本上,事实证明,这比我预期的要长得多,但是我认为我们设法经历了所有要点,至少在“确定”方式。

Final result.

这是通过在我们的路径中遵循所有面包屑获得的结果……但是也许您注意到它有点生,对吧?如何改进这个项目?这是一些想法:

  • 白天/晚上;
  • 雪(已包含在此 Asset 中);
  • 实时城市搜索;
  • 显示它是多少度;
  • 不时致电API以更新天气。

我什至在演示中实现了其中的一些想法,因此,如果您想稍微播放,只是click here,您可以在my GitHub上找到所有后端代码。顺便说一句,您知道我如何免费将Unity构建和我的API免费放在互联网上吗?我认为这是在此处发布的好主题...

无论如何,谢谢大家的关注,如果您有任何问题,疑问或建议,我全都是耳朵。

ðºPhartmentgments