首页 > 编程语言 >[9] UE C++ Snake

[9] UE C++ Snake

时间:2024-04-17 19:33:24浏览次数:35  
标签:SpawnLocation Food void C++ Owner GetActorLocation Snake SnakeBodyPointer UE

思维导图

背景地图制作

创建瓦片集

角色素材

GameMode功能

游戏开始控制食物的生成

食物生成池(性能优化)

/*
 *形参如果是一个引用,且没有添加const关键字,代表实参想要借助形参修改值
 * param 是否指定生成时候的地址
 */
void ASnakeGameModeBase::SpawnFood(FVector& SpawnLocation)
{
   AFoodActor* Food = nullptr;
   SpawnLocation.X = FMath::RandRange(-7950.f, 7950.f);
   SpawnLocation.Y = 0;
   SpawnLocation.Z = FMath::RandRange(-7950.f, 7950.f);
   //Food=GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass());
   Food = GetFoodInstance(); //没有参数的函数
   Food->SetActorLocation(SpawnLocation);
}

/*
 * 函数调用者如果想提供位置,就调用这个函数
 */
AFoodActor* ASnakeGameModeBase::GetFoodInstance()
{
   AFoodActor* Food = nullptr;
   //判断数组(存放已经需要Destroy也就是被吃掉的废弃食物的容器)中有没有元素,有,Food赋值为废弃食物的地址
   if (EatFoodPointerArray.Num())
   {
      Food = EatFoodPointerArray.Last(); //获取到数组中最后一个元素(废弃食物的地址)
      EatFoodPointerArray.RemoveAt(EatFoodPointerArray.Num() - 1); //因为废弃的食物已经被重新使用了,所以移除数组
      //Food->SetFoodSprite();//重置素材的方法1:直接获取到素材并设置
      Food->RenderFood->SetSprite(Food->GetSprite()); //重置素材的方法2:找到组件并调用函数,设置给组件素材
   }
   return Food ? Food : GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass());
   //Food如果存在一个值,代表需要返回废弃食物,如果为nullptr,代表数组中已经没有废弃食物使用了,就重新生成一个
}

/*
 * 函数调用者如果不想提供位置,就调用这个函数
 */
AFoodActor* ASnakeGameModeBase::GetFoodInstance(FVector& SpawnLocation)
{
   AFoodActor* Food = nullptr;
   SpawnLocation.X = FMath::RandRange(-7950.f, 7950.f);
   SpawnLocation.Y = 0;
   SpawnLocation.Z = FMath::RandRange(-7950.f, 7950.f);

   //判断数组(存放已经需要Destroy也就是被吃掉的废弃食物的容器)中有没有元素,有,Food赋值为废弃食物的地址
   if (EatFoodPointerArray.Num())
   {
      Food = EatFoodPointerArray.Last(); //获取到数组中最后一个元素(废弃食物的地址)
      EatFoodPointerArray.RemoveAt(EatFoodPointerArray.Num() - 1); //因为废弃的食物已经被重新使用了,所以移除数组
      Food->SetActorLocation(SpawnLocation);
      //Food->SetFoodSprite();//重置素材的方法1:直接获取到素材并设置
      Food->RenderFood->SetSprite(Food->GetSprite()); //重置素材的方法2:找到组件并调用函数,设置给组件素材
   }
   return Food
             ? Food
             : GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass(), SpawnLocation, FRotator::ZeroRotator);
}

 减少场景中食物的数量

void ASnakeGameModeBase::SubFood()
{
   CurrentFoodCounter--;
   if (CurrentFoodCounter <= 1950) //如果场景中的食物数量到达1970就重新生成食物
   {
      FVector SpawnLocation;
      for (int32 i = 0; i < 50; i++)
      {
         SpawnFood(SpawnLocation);
      }
      CurrentFoodCounter += 50;
   }
}

SnakePawn 蛇身蛇头

生成蛇的身体逻辑

//SnakePawn.cpp 
/*
 * SnakeBodyPointer 蛇生指针存在代表还有后续蛇身
 */
void ASnakePawn::SpawnBody()
{
	if (CurrentSnakeState != ESnakeState::Free)return;
	ASnakeBodyActor* SpawnBody = GetWorld()->SpawnActor<ASnakeBodyActor>(ASnakeBodyActor::StaticClass()); //生成
	SpawnBody->SetBodySkinIndex(SnakeHeadSkinIndex);
	SpawnBody->SetSpawnThisBodyHead(this);
	SpawnBodyArray.Add(SpawnBody);

	//找位置
	if (SnakeBodyPointer)
	{
		//头后有一节蛇身? 找第一节蛇身开始询问指针是否为空(身后有没有一节身体)
		SnakeBodyPointer->SetSnakeBody(SpawnBody);
	}
	else
	{ //设置第一节蛇尾
		SnakeBodyPointer = SpawnBody;
		//SpawnBody->SetActorLocation(GetActorLocation()+RenderSnakeHead->GetUpVector()*-60);
		SpawnBody->SetActorLocation(GetActorLocation());
	}
}

//SnakeBodyActor.cpp 链式调用查找最后一个尾巴位置
void ASnakeBodyActor::SetSnakeBody(ASnakeBodyActor* Body)
{   if(SnakeBodyPointer)//当前类实例化出的对象是不是身后还有一节蛇身?
	{
		SnakeBodyPointer->SetSnakeBody(Body);
	}else
	{
		SnakeBodyPointer=Body;
		//Body->SetActorLocation(GetActorLocation()+GetActorUpVector()*-45);
		Body->SetActorLocation(GetActorLocation());//出生的位置是直接放在了上一节的身上
	}
}

 蛇移动 , 蛇身跟随的逻辑

//数组:存储蛇头走过的每一个点(点越多,蛇身与蛇头之间的间距就更大)
//TArray<FVector> SnakeMovePositionArray;
//SnakePawn.cpp
void ASnakePawn::UpdateSnakeMove(float DeltaTime)
{
   if (CurrentSnakeState != ESnakeState::Free)return;
   FVector _SpeedAndDirection = RenderSnakeHead->GetUpVector() * DeltaTime * MoveSpeed;
   AddActorLocalOffset(_SpeedAndDirection);
   // 移动完成之后进行位置的监测(如果碰到上边界就执行死亡逻辑)
   if (GetActorLocation().Z >= 7990)
   {
          //修改蛇的状态为死亡
          ChangeSnakeState(ESnakeState::Dead);
       }
   if (SnakeBodyPointer)//存在代表有蛇尾
   {
      SnakeBodyPointer->SnakeMovePositionArray.Add(GetActorLocation()); //Tick每执行一次,就记录一次蛇头的位置
   }
}
//SnakeBodyActor.cpp
void ASnakeBodyActor::SnakeBodyFollowSnakeHead()
{
	if(SnakeMovePositionArray.Num()>=15)//蛇头如果已经走出去10帧 了
	{
		//当前一节身体跟上
		SetActorLocation(SnakeMovePositionArray[0]);
		//自己走一帧 告知自己的下一节身体当前的位置(向下一节身体数组中添加一个位置)
		if(SnakeBodyPointer)
		{
			SnakeBodyPointer->SnakeMovePositionArray.Add(SnakeMovePositionArray[0]);
		}
		//因为SnakeMovePositionArray[0]这个点已经使用完成(自己更新了位置,并将这个位置告知了下一节身体),这就是一个废弃的点了
		SnakeMovePositionArray.RemoveAt(0);//移除数组中下标为0 的值(坐标点)
	}
}

Interface

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "SnakeInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class USnakeInterface : public UInterface
{
   GENERATED_BODY()
};

/**
 * 
 */
class SNAKE_API ISnakeInterface
{
   GENERATED_BODY()
public:
   UFUNCTION(/*BlueprintCallable,*/BlueprintNativeEvent)
   class ASnakePawn* GetSnake();
};

AISnakeActorComponent AI组件

//Pawn.cpp 判断当前对象是否AI , Ai则新建一个组件附着到上面
void ASnakePawn::BeginPlay()
{
   Super::BeginPlay();
   UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetViewTarget(this); //切换相机为当前类的相机

   //对象生成之后就开始判断这个对象的类型(玩家控制的角色,非玩家控制的角色)
   if (Cast<APlayerController>(GetController()))
   {
      SnakeHeadFlipbook = LoadObject<UPaperFlipbook>(
         nullptr,TEXT("/Script/Paper2D.PaperFlipbook'/Game/Snake/Animation/SN3/PF_SN3.PF_SN3'"));
      if (SnakeHeadFlipbook)
         RenderSnakeHead->SetFlipbook(SnakeHeadFlipbook);
      SnakeHeadSkinIndex = 3;
   }
   else
   {
      SnakeHeadSkinIndex = FMath::RandRange(1, 3);
      SnakeHeadFlipbook = LoadObject<UPaperFlipbook>(
         nullptr,TEXT("/Script/Paper2D.PaperFlipbook'/Game/Snake/Animation/SN1/PF_SN1.PF_SN1'"));
      if (SnakeHeadFlipbook)
         RenderSnakeHead->SetFlipbook(SnakeHeadFlipbook);
      AISnake = NewObject<UAISnakeActorComponent>(this);
      AISnake->RegisterComponent();
   }

   SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &ASnakePawn::OnComponentBeginOverlap);
   SphereComponent->OnComponentEndOverlap.AddDynamic(this, &ASnakePawn::OnComponentEndOverlap);
}
/*
.h
*/
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "AISnakeActorComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SNAKE_API UAISnakeActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UAISnakeActorComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// 这个类的作用是扩展功能使用的

	//寻找扩展对象:当前组件类实例化出的对象是在哪个类下的成员
	class ASnakePawn* Owner;

	void CheckEnemy();//检测敌方
	//制定检测时间
	UPROPERTY()
    float CheckTimeOffset;//时间累加值

	
};

/*
.cpp
*/
// Fill out your copyright notice in the Description page of Project Settings.


#include "AISnakeActorComponent.h"

#include "SnakePawn.h"

// Sets default values for this component's properties
UAISnakeActorComponent::UAISnakeActorComponent()
{
   // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
   // off to improve performance if you don't need them.
   PrimaryComponentTick.bCanEverTick = true;

   // ...
}


// Called when the game starts
void UAISnakeActorComponent::BeginPlay()
{   Super::BeginPlay();
    Owner= Cast<ASnakePawn>(GetOwner());//得到附着对象目标
   if(Owner)
   {
      UE_LOG(LogTemp, Log, TEXT("AIComponent==========="))
   }
}


// Called every frame
void UAISnakeActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{  Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
   if((CheckTimeOffset+=DeltaTime)>=0.3f)
   {
      CheckTimeOffset=0;
      CheckEnemy();
   }
}
void UAISnakeActorComponent::CheckEnemy()
{
   if(!Owner)return;//有没有主人
   if(Owner->CurrentSnakeState!=ESnakeState::Free)return;//主人是不是正常移动的状态
   TArray< FHitResult> OutHits;
   FCollisionShape Shape;
   Shape.SetSphere(58.f);
   bool bHit = Owner->GetWorld()->SweepMultiByChannel(OutHits,
   Owner->GetActorLocation(),Owner->GetActorLocation(),FQuat(0,0,0,0),  ECollisionChannel::ECC_Camera,Shape);
   if(bHit)
   {
      for(const auto& Hit:OutHits)
      {
         if(IsValid(Hit.GetActor()))
         {
            //判断是不是蛇(蛇头  蛇身)
            ISnakeInterface* Snake=Cast<ISnakeInterface>(Hit.GetActor());
            if(Snake) //碰到的是蛇
            {
               ASnakePawn* Pawn = Snake->Execute_GetSnake(Hit.GetActor());//调用函数,得到蛇头的地址
               //判断是不是自身(自身的蛇身)
               if(Pawn==Owner)
               {
                  //是自身
               }else
               {
                  //不是自身:告知主人,转头就跑
                  Owner->bAIRunaway=true;
                  FVector RunawayDir = Owner->GetActorLocation()-Pawn->GetActorLocation();
                  if(RunawayDir.Size()>=40.f)
                  {
                     RunawayDir.Normalize();
                     Owner->AIRunawayRotator = RunawayDir.Rotation();
                  }
                  else
                  {
                     Owner->ChangeSnakeState(ESnakeState::Dead);
                  }
                  
               }
            }
         }
      }
   }
}

 

标签:SpawnLocation,Food,void,C++,Owner,GetActorLocation,Snake,SnakeBodyPointer,UE
From: https://www.cnblogs.com/Aquakinn/p/18141054

相关文章

  • [ABC208D] Shortest Path Queries 2 题解
    [ABC208D]ShortestPathQueries2题解思路解析此题的本质其实就是Floyd。我们在进行Floyd时会有一个\(k\)充当中间点,可见这里的\(k\)就等于题目当中的\(k\),因为小于等于\(k\)的所有点都被当作过中间点转移过,而大于\(k\)的所有点都没有被当作过中间点转移过,于是直......
  • PostgreSql: ERROR: value too long for type character varying(1) 定位字段方法
    报错原因设置的数据库字段长度为1,但实际的值超过规定字段,导致报错。解决方案首先,需要定位字段是哪个字段出现的报错,但可惜的是,并没有报出具体是哪个字段在报错,所以只能通过检查Schema,查看哪些字段是长度为1的,然后再进行值的比较,才能锁定位置。ERROR:valuetoolongfortype......
  • Laravel Eloquent Paginator 一种优雅的重新修改分页数据的方法
    需求如何将分页器数据内的数据进行处理后再塞回去解决分页器数据是一个Collection,使用transform方法进行处理$paginator=$this->items()->where('position','=',null)->paginate(15);$paginator->getCollection()->transform(function($value){//Yourcodehe......
  • 通过innerHTML vue不起作用
    innerHTML 在Vue中直接使用可能不会如预期工作,因为Vue是数据驱动的,它期望你通过操作数据来更新视图,而不是直接操作DOM。当你使用 v-html 指令时,Vue会将内容作为HTML解析,但如果你直接修改DOM的 innerHTML,Vue就无法追踪变动了。解决方法:使用Vue的数据绑定功能......
  • 解决C# 连接MYSQL数据库查询数据时 Unable to convert MySQL date/time value to Syst
    C#读取MySql时,如果存在字段类型为date/datetime时的可能会出现以下问题“UnabletoconvertMySQLdate/timevaluetoSystem.DateTime”原因:可能是该字段(date/datetime)的值默认缺省值为:0000-00-00/0000-00-0000:00:00,这样的数据读出来转换成System.DateTime时就会有问题;解......
  • vue中websocket的使用---详解
    一、什么是webscoketWebSockets 是一种先进的技术,它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。 WebSockets这种技术中有一个接口名为WebSocket,它是一个用于连接WebSoc......
  • 点击菜单生成tabs(vue3.0)
    1.安装vuex npminstallvuex@next--save在main.js中引用vuex2.在main.js同级目录新建store/store.js文件 代码:import{createStore}from'vuex'exportdefaultcreateStore({ state:{ tabsList:[] }, mutations:{ addTab(state,tab){ //判断是否......
  • 08 Vue3项目搭建后台管理系统
    项目配置elementPlus1.下载安装npminstallelement-plus@element-plus/icons-vue2.main.ts全局注册import{createApp}from'vue';import{createPinia}from'pinia';//1.引入elementPlusimportElementPlusfrom'element-plus';//......
  • 编写ROS2(C++语言)软件包的步骤
    0简介介绍编写ROS2(C++语言)软件包的步骤;0.1前置条件参考x.1,和x.2,安装ROS2和编译工具;1创建ROS2软件包以下的指令,创建一个名为mtuav-sns-radar-ros2的ROS2软件包,使用ament_cmake作为构建系统,许可证类型为Apache-2.0,并包含一个名为radar_node的节点;mkdir-p~/ros2_ws/srccd......
  • LeetCode 1315. Sum of Nodes with Even-Valued Grandparent
    原题链接在这里:https://leetcode.com/problems/sum-of-nodes-with-even-valued-grandparent/description/题目:Giventhe root ofabinarytree,return thesumofvaluesofnodeswithan even-valuedgrandparent.Iftherearenonodeswithan even-valuedgrandpar......