static private bool BoxHitsTerrain(NativeArray <TerrainSystem.AABB> blockBoxes, int2 terrainSize, TerrainSystem.AABB box) { // check nearby boxes -- TODO bias by half a box int xMin = (int)math.floor((box.Min.x + 0.5f) / TerrainSystem.BOX_SPACING); int xMax = (int)math.floor((box.Max.x + 0.5f) / TerrainSystem.BOX_SPACING); int zMin = (int)math.floor((box.Min.z + 0.5f) / TerrainSystem.BOX_SPACING); int zMax = (int)math.floor((box.Max.z + 0.5f) / TerrainSystem.BOX_SPACING); xMin = math.max(0, xMin); xMax = math.min(terrainSize.x - 1, xMax); zMin = math.max(0, zMin); zMax = math.min(terrainSize.y - 1, zMax); for (int z = zMin; z <= zMax; z++) { for (int x = xMin; x <= xMax; x++) { int blockIndex = TerrainSystem.BlockCoordsToIndex(new int2(x, z), terrainSize); TerrainSystem.AABB blockBox = blockBoxes[blockIndex]; bool intersects = !(box.Max.x <blockBox.Min.x || box.Min.x> blockBox.Max.x || box.Max.y <blockBox.Min.y || box.Min.y> blockBox.Max.y || box.Max.z <blockBox.Min.z || box.Min.z> blockBox.Max.z); if (intersects) { return(true); } } } // TODO: check tanks return(false); }
protected override JobHandle OnUpdate(JobHandle inputDeps) { if (_options == null) { _options = GameObject.Find("Options").GetComponent <Options>(); } // Determine which block is under the mouse cursor. float meanBlockHeight = (_options.terrainHeightMin + _options.terrainHeightMax) / 2; Vector3 mouseWorldPos = new Vector3(0, meanBlockHeight, 0); Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); float t = (meanBlockHeight - ray.origin.y) / ray.direction.y; mouseWorldPos.x = ray.origin.x + t * ray.direction.x; mouseWorldPos.z = ray.origin.z + t * ray.direction.z; inputDeps = JobHandle.CombineDependencies(inputDeps, _terrain.UpdateCacheJob); int2 mouseBoxCoords = TerrainSystem.PositionToBlockCoords(mouseWorldPos.x, mouseWorldPos.z, _terrain.TerrainSize); return(new UpdateBounceJob { ElapsedTime = Time.time, MouseBoxCoords = mouseBoxCoords, TerrainSize = new int2(_options.terrainSizeX, _options.terrainSizeZ), BlockHeights = _terrain.CachedBlockHeights, IsBlockOccupied = _terrain.CachedIsBlockOccupied, }.Schedule(_playerQuery, inputDeps)); }
public unsafe void Execute(Entity entity, int entityIndex, ref Translation cannonballPos, ref ArcState arc) { float t = ElapsedTime - arc.StartTime; if (t >= 0 && t < arc.Duration) { // Test for collisions with the player const float COLLISION_DISTANCE_SQ = (PLAYER_RADIUS + TankFireSystem.CANNONBALL_RADIUS) * (PLAYER_RADIUS + TankFireSystem.CANNONBALL_RADIUS); if (math.lengthsq(cannonballPos.Value - PlayerCenterPosition) < COLLISION_DISTANCE_SQ) { if (!EnableInvincibility) { // game over! add a NewGame entity to trigger the NewGameSystem to restart. Entity gameOverEntity = CommandBuffer.CreateEntity(entityIndex); CommandBuffer.AddComponent(entityIndex, gameOverEntity, new NewGameTag()); } } // Get the current height along the parabola & update cannonball pos float s = t / arc.Duration; cannonballPos.Value = new float3( math.lerp(arc.StartPos.x, arc.EndPos.x, s), Parabola.Solve(arc.Parabola.x, arc.Parabola.y, arc.Parabola.z, s), math.lerp(arc.StartPos.z, arc.EndPos.z, s) ); } else if (t >= arc.Duration) { // Cannonball has reached the end of its path; damage the block it hit // TODO(@cort): If the destination block has been damaged/lowered since the shot was fired, the shot will "land" in the air above the block. Original game does this too. int2 boxCoords = TerrainSystem.PositionToBlockCoords(cannonballPos.Value.x, cannonballPos.Value.z, TerrainSize); int boxIndex = TerrainSystem.BlockCoordsToIndex(boxCoords, TerrainSize); Entity boxEntity = BlockEntities[boxIndex]; int * blockHitsPtr = (int *)BlockHitCounts.GetUnsafePtr() + boxIndex; int newCount = Interlocked.Increment(ref *blockHitsPtr); if (newCount == 1) { CommandBuffer.AddComponent(entityIndex, boxEntity, new UpdateBlockTransformTag()); } // TODO(@cort): There is a (relatively benign) race condition here. There's no guarantee that the last job // to update the hit counts array will also write the last command that updates the BlockHeight component. // This means that if >1 cannonball hits a block in a single frame, some of the hits may not damage the block. // One solution would be commands that allow mutating component values rather than setting them to a value // known at record time. https://github.com/Unity-Technologies/dots/issues/632 float currentBoxHeight = BlockHeights[boxIndex]; CommandBuffer.SetComponent(entityIndex, boxEntity, new BlockHeight { Value = math.max(BLOCK_HEIGHT_MIN, currentBoxHeight - newCount * BoxHeightDamage) }); // destroy the cannonball CommandBuffer.DestroyEntity(entityIndex, entity); } }
protected override void OnCreateManager() { _cannonballQuery = GetEntityQuery(new ComponentType[] { ComponentType.ReadWrite <Translation>(), ComponentType.ReadWrite <ArcState>(), ComponentType.ReadWrite <Scale>(), }); _terrain = World.GetOrCreateSystem <TerrainSystem>(); _barrier = World.GetOrCreateSystem <BeginSimulationEntityCommandBufferSystem>(); _playerPosCache = World.GetOrCreateSystem <PlayerPositionCacheSystem>(); }
protected override void OnCreateManager() { _playerQuery = GetEntityQuery(new ComponentType[] { ComponentType.ReadWrite <Translation>(), ComponentType.ReadWrite <ArcState>(), ComponentType.Exclude <Scale>(), }); _terrain = World.GetOrCreateSystem <TerrainSystem>(); _tankAimSystem = World.GetOrCreateSystem <TankAimSystem>(); }
protected override void OnCreateManager() { _terrain = World.GetOrCreateSystem <TerrainSystem>(); _playerPosCache = World.GetOrCreateSystem <PlayerPositionCacheSystem>(); _barrier = World.GetOrCreateSystem <BeginSimulationEntityCommandBufferSystem>(); }
public void Execute(Entity _, int index, [ReadOnly] ref LocalToWorld localToWorld, ref TankFireCountdown countdown, ref Rotation rotation) { // launching cannonball countdown.SecondsUntilFire -= DeltaTime; if (countdown.SecondsUntilFire <= 0) { countdown.SecondsUntilFire = math.fmod(countdown.SecondsUntilFire, FirePeriod) + FirePeriod; // start and end positions int2 playerBoxCoords = TerrainSystem.PositionToBlockCoords(PlayerPosition.x, PlayerPosition.z, TerrainSize); int playerBoxIndex = TerrainSystem.BlockCoordsToIndex(playerBoxCoords, TerrainSize); float playerBoxHeight = BlockHeights[playerBoxIndex]; float3 startPos = math.mul(localToWorld.Value, new float4(0, 0, 0, 1)).xyz; float3 endPos = TerrainSystem.BlockCoordsToPosition(playerBoxCoords, playerBoxHeight, TerrainSize); endPos.y += CANNONBALL_RADIUS + 0.01f; // extra 0.01 to avoid false positive collisions at endPos float distance = math.distance(endPos.xz, startPos.xz); float duration = distance / CANNONBALL_SPEED; if (duration < 0.0001f) { duration = 1.0f; } // binary searching to determine height of cannonball arc float low = math.max(startPos.y, endPos.y); float high = low * 2; float paraA, paraB, paraC; // look for height of arc that won't hit boxes while (true) { Parabola.Create(startPos.y, high, endPos.y, out paraA, out paraB, out paraC); if (!PathHitsTerrain(BlockBoundingBoxes, TerrainSize, startPos, endPos, paraA, paraB, paraC)) { // high enough break; } // not high enough. Double value low = high; high *= 2; // failsafe if (high > 9999) { return; // skip launch } } // do binary searches to narrow down while (high - low > CANNONBALL_PARABOLA_PRECISION) { float mid = (low + high) / 2; Parabola.Create(startPos.y, mid, endPos.y, out paraA, out paraB, out paraC); if (PathHitsTerrain(BlockBoundingBoxes, TerrainSize, startPos, endPos, paraA, paraB, paraC)) { // not high enough low = mid; } else { // too high high = mid; } } // launch with calculated height float height = (low + high) / 2; Parabola.Create(startPos.y, height, endPos.y, out paraA, out paraB, out paraC); Entity ballEntity = CommandBuffer.Instantiate(index, CannonballPrefab); CommandBuffer.SetComponent(index, ballEntity, new Translation { Value = startPos }); CommandBuffer.SetComponent(index, ballEntity, new ArcState { Parabola = new float3(paraA, paraB, paraC), StartTime = ElapsedTime, Duration = duration, StartPos = startPos, EndPos = endPos, }); // set cannon rotation { const float t = 0.01f; float altitudeAngle = -math.atan2( Parabola.Solve(paraA, paraB, paraC, t) - Parabola.Solve(paraA, paraB, paraC, 0.0f), t * distance); rotation.Value = quaternion.Euler(altitudeAngle, 0, 0); } } }
public void Execute(ref Translation position, ref ArcState arc) { float t = ElapsedTime - arc.StartTime; if (t > arc.Duration) { // Compute the next arc info int2 playerCoords = TerrainSystem.PositionToBlockCoords(position.Value.x, position.Value.z, TerrainSize); int2 dir = math.clamp(MouseBoxCoords - playerCoords, new int2(-1, -1), new int2(1, 1)); int2 destCoords = playerCoords + dir; // If destination is outside the terrain bounds, don't move. if (destCoords.x < 0 || destCoords.x >= TerrainSize.x || destCoords.y < 0 || destCoords.y >= TerrainSize.y) { destCoords = playerCoords; } int playerBlockIndex = TerrainSystem.BlockCoordsToIndex(playerCoords, TerrainSize); int destBlockIndex = TerrainSystem.BlockCoordsToIndex(destCoords, TerrainSize); // If destination is occupied, don't move. if (IsBlockOccupied[destBlockIndex] != 0) { destCoords = playerCoords; destBlockIndex = playerBlockIndex; } float playerBlockHeight = BlockHeights[playerBlockIndex]; float destBlockHeight = BlockHeights[destBlockIndex]; float parabolaHeight = math.max(playerBlockHeight, destBlockHeight); if (dir.x != 0 && dir.y != 0) { // If jumping diagonally, the parabola height must take into account the directly // adjacent blocks as well. int2 adjacentCoordsX = playerCoords + new int2(dir.x, 0); if (adjacentCoordsX.x >= 0 && adjacentCoordsX.x < TerrainSize.x) { int adjacentIndexX = TerrainSystem.BlockCoordsToIndex(adjacentCoordsX, TerrainSize); float adjacentHeightX = BlockHeights[adjacentIndexX]; parabolaHeight = math.max(parabolaHeight, adjacentHeightX); } int2 adjacentCoordsZ = playerCoords + new int2(0, dir.y); if (adjacentCoordsZ.y >= 0 && adjacentCoordsZ.y < TerrainSize.y) { int adjacentIndexZ = TerrainSystem.BlockCoordsToIndex(adjacentCoordsZ, TerrainSize); float adjacentHeightZ = BlockHeights[adjacentIndexZ]; parabolaHeight = math.max(parabolaHeight, adjacentHeightZ); } } parabolaHeight += BOUNCE_HEIGHT; Parabola.Create(playerBlockHeight, parabolaHeight, destBlockHeight, out arc.Parabola.x, out arc.Parabola.y, out arc.Parabola.z); arc.StartPos = TerrainSystem.BlockCoordsToPosition(playerCoords, playerBlockHeight, TerrainSize); arc.EndPos = TerrainSystem.BlockCoordsToPosition(destCoords, destBlockHeight, TerrainSize); arc.StartTime = ElapsedTime; float distance = math.distance(playerCoords, destCoords); arc.Duration = math.max(1, distance) * BOUNCE_BASE_DURATION; } else { // Get the current height along the parabola float s = t / arc.Duration; position.Value = new float3( math.lerp(arc.StartPos.x, arc.EndPos.x, s), Parabola.Solve(arc.Parabola.x, arc.Parabola.y, arc.Parabola.z, s), math.lerp(arc.StartPos.z, arc.EndPos.z, s) ); } }